fix: tun2socks auth settings (#2456)

* add parser auth/pass & fix port

* fix generateRandomHex

* remove hardcore port ios

* add generated random port

* fix sin6_port

* fixed inbound

* add error message

* add std::runtime_error & fixed random generator

* remove loop

---------

Co-authored-by: Yaumenau Pavel <yaumenau.pavel@planetvpn.dev>
This commit is contained in:
yp
2026-04-13 15:06:08 +03:00
committed by GitHub
parent a75bd0cf5e
commit 92deee5f67
7 changed files with 341 additions and 12 deletions

View File

@@ -4,6 +4,9 @@ import android.content.Context
import android.net.VpnService.Builder import android.net.VpnService.Builder
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.net.InetAddress
import java.net.ServerSocket
import java.util.UUID
import go.Seq import go.Seq
import org.amnezia.vpn.protocol.BadConfigException import org.amnezia.vpn.protocol.BadConfigException
import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.Protocol
@@ -19,11 +22,32 @@ import org.amnezia.vpn.util.Log
import org.amnezia.vpn.util.net.InetNetwork import org.amnezia.vpn.util.net.InetNetwork
import org.amnezia.vpn.util.net.ip import org.amnezia.vpn.util.net.ip
import org.amnezia.vpn.util.net.parseInetAddress import org.amnezia.vpn.util.net.parseInetAddress
import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
private const val TAG = "Xray" private const val TAG = "Xray"
private const val LIBXRAY_TAG = "libXray" private const val LIBXRAY_TAG = "libXray"
private fun findSocksInboundIndex(inbounds: JSONArray): Int {
for (i in 0 until inbounds.length()) {
val o = inbounds.optJSONObject(i) ?: continue
if (o.optString("protocol").equals("socks", ignoreCase = true)) {
return i
}
}
return -1
}
private fun acquireFreeLocalPort(): Int {
try {
ServerSocket(0, 1, InetAddress.getByName("127.0.0.1")).use { return it.localPort }
} catch (e: Exception) {
throw VpnStartException(
"Failed to acquire free TCP port on 127.0.0.1 for SOCKS inbound: ${e.message}"
)
}
}
class Xray : Protocol() { class Xray : Protocol() {
private var isRunning: Boolean = false private var isRunning: Boolean = false
@@ -56,6 +80,10 @@ class Xray : Protocol() {
val xrayJsonConfig = config.optJSONObject("xray_config_data") val xrayJsonConfig = config.optJSONObject("xray_config_data")
?: config.optJSONObject("ssxray_config_data") ?: config.optJSONObject("ssxray_config_data")
?: throw BadConfigException("config_data not found") ?: throw BadConfigException("config_data not found")
// Inject SOCKS5 auth before starting xray. Re-uses existing credentials if present.
ensureInboundAuth(xrayJsonConfig)
val xrayConfig = parseConfig(config, xrayJsonConfig) val xrayConfig = parseConfig(config, xrayJsonConfig)
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) }) (xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
@@ -97,9 +125,22 @@ class Xray : Protocol() {
if (it.isNotBlank()) setMtu(it.toInt()) if (it.isNotBlank()) setMtu(it.toInt())
} }
val socksConfig = xrayJsonConfig.getJSONArray("inbounds")[0] as JSONObject val inbounds = xrayJsonConfig.getJSONArray("inbounds")
val socksIdx = findSocksInboundIndex(inbounds)
if (socksIdx < 0) {
throw BadConfigException("socks inbound not found")
}
val socksConfig = inbounds.getJSONObject(socksIdx)
socksConfig.getInt("port").let { setSocksPort(it) } socksConfig.getInt("port").let { setSocksPort(it) }
val socksSettings = socksConfig.optJSONObject("settings")
val accounts = socksSettings?.optJSONArray("accounts")
if (accounts != null && accounts.length() > 0) {
val account = accounts.getJSONObject(0)
setSocksUser(account.optString("user"))
setSocksPass(account.optString("pass"))
}
configSplitTunneling(config) configSplitTunneling(config)
configAppSplitTunneling(config) configAppSplitTunneling(config)
} }
@@ -162,9 +203,10 @@ class Xray : Protocol() {
} }
private fun runTun2Socks(config: XrayConfig, fd: Int) { private fun runTun2Socks(config: XrayConfig, fd: Int) {
val proxyUrl = "socks5://${config.socksUser}:${config.socksPass}@127.0.0.1:${config.socksPort}"
val tun2SocksConfig = Tun2SocksConfig().apply { val tun2SocksConfig = Tun2SocksConfig().apply {
mtu = config.mtu.toLong() mtu = config.mtu.toLong()
proxy = "socks5://127.0.0.1:${config.socksPort}" proxy = proxyUrl
device = "fd://$fd" device = "fd://$fd"
logLevel = "warn" logLevel = "warn"
} }
@@ -173,6 +215,37 @@ class Xray : Protocol() {
} }
} }
// Ensures SOCKS5 auth is present on the socks inbound settings.
// Re-uses existing credentials if already configured; otherwise generates random ones.
private fun ensureInboundAuth(xrayConfig: JSONObject) {
val inbounds = xrayConfig.optJSONArray("inbounds") ?: return
val socksIdx = findSocksInboundIndex(inbounds)
if (socksIdx < 0) return
val inbound = inbounds.getJSONObject(socksIdx)
inbound.put("port", acquireFreeLocalPort())
val settings = inbound.optJSONObject("settings") ?: JSONObject().also { inbound.put("settings", it) }
val accounts = settings.optJSONArray("accounts")
if (accounts != null && accounts.length() > 0) {
val account = accounts.getJSONObject(0)
if (account.optString("user").isNotEmpty() && account.optString("pass").isNotEmpty()) {
// Ensure auth mode is enforced even for imported configs that had accounts
// but auth: "noauth" (or no auth field).
settings.put("auth", "password")
inbound.put("settings", settings)
inbounds.put(socksIdx, inbound)
return
}
}
val user = UUID.randomUUID().toString().replace("-", "").substring(0, 16)
val pass = UUID.randomUUID().toString().replace("-", "")
settings.put("auth", "password")
settings.put("accounts", JSONArray().put(JSONObject().put("user", user).put("pass", pass)))
inbound.put("settings", settings)
inbounds.put(socksIdx, inbound)
}
companion object { companion object {
val instance: Xray by lazy { Xray() } val instance: Xray by lazy { Xray() }
} }

View File

@@ -9,12 +9,16 @@ private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
class XrayConfig protected constructor( class XrayConfig protected constructor(
protocolConfigBuilder: ProtocolConfig.Builder, protocolConfigBuilder: ProtocolConfig.Builder,
val socksPort: Int, val socksPort: Int,
val socksUser: String,
val socksPass: String,
val maxMemory: Long, val maxMemory: Long,
) : ProtocolConfig(protocolConfigBuilder) { ) : ProtocolConfig(protocolConfigBuilder) {
protected constructor(builder: Builder) : this( protected constructor(builder: Builder) : this(
builder, builder,
builder.socksPort, builder.socksPort,
builder.socksUser,
builder.socksPass,
builder.maxMemory builder.maxMemory
) )
@@ -22,6 +26,12 @@ class XrayConfig protected constructor(
internal var socksPort: Int = 0 internal var socksPort: Int = 0
private set private set
internal var socksUser: String = ""
private set
internal var socksPass: String = ""
private set
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
private set private set
@@ -29,6 +39,10 @@ class XrayConfig protected constructor(
fun setSocksPort(port: Int) = apply { socksPort = port } fun setSocksPort(port: Int) = apply { socksPort = port }
fun setSocksUser(user: String) = apply { socksUser = user }
fun setSocksPass(pass: String) = apply { socksPass = pass }
fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory } fun setMaxMemory(maxMemory: Long) = apply { this.maxMemory = maxMemory }
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) } override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }

View File

@@ -1,6 +1,11 @@
#include <QString> #include <QString>
#include <QJsonArray>
#include <QJsonObject> #include <QJsonObject>
#include <QList> #include <QList>
#include <QHostAddress>
#include <QRandomGenerator>
#include <QTcpServer>
#include <stdexcept>
#include "3rd/QJsonStruct/QJsonIO.hpp" #include "3rd/QJsonStruct/QJsonIO.hpp"
#include "transfer.h" #include "transfer.h"
#include "serialization.h" #include "serialization.h"
@@ -14,25 +19,125 @@ namespace amnezia::serialization::inbounds
// "port": 10808, // "port": 10808,
// "protocol": "socks", // "protocol": "socks",
// "settings": { // "settings": {
// "auth": "password",
// "accounts": [{"user": "...", "pass": "..."}],
// "udp": true // "udp": true
// } // }
// } // }
//], //],
const static QString listen = "127.0.0.1"; const static QString listen = "127.0.0.1";
const static int port = 10808; const static int defaultPort = 10808;
const static QString protocol = "socks"; const static QString protocol = "socks";
static int indexOfSocksInbound(const QJsonArray &inbounds)
{
for (int i = 0; i < inbounds.size(); ++i) {
const QString p = inbounds.at(i).toObject().value(QLatin1String("protocol")).toString();
if (p.compare(QLatin1String("socks"), Qt::CaseInsensitive) == 0)
return i;
}
return -1;
}
// Ask the OS for a free TCP port on loopback (same stack as inbound "listen": "127.0.0.1").
static int acquireFreeLocalPort()
{
QTcpServer probe;
if (!probe.listen(QHostAddress(QStringLiteral("127.0.0.1")), 0)) {
throw std::runtime_error(
"Failed to bind a local TCP port on 127.0.0.1 for SOCKS inbound "
"(QTcpServer::listen failed; possible permission or OS network error).");
}
return static_cast<int>(probe.serverPort());
}
// Generates a hex string of `byteCount` random bytes (URL-safe, no special chars).
static QString generateRandomHex(int byteCount)
{
if (byteCount <= 0)
return {};
// fillRange writes full quint32 words; size the buffer to a multiple of 4 bytes to avoid
// overrunning a short buffer when byteCount is not divisible by 4.
const int numUint32 = (byteCount + int(sizeof(quint32)) - 1) / int(sizeof(quint32));
QByteArray buf(numUint32 * int(sizeof(quint32)), '\0');
QRandomGenerator::system()->fillRange(reinterpret_cast<quint32 *>(buf.data()), numUint32);
return QString::fromLatin1(buf.left(byteCount).toHex());
}
QJsonObject GenerateInboundEntry() QJsonObject GenerateInboundEntry()
{ {
QJsonObject root; QJsonObject root;
QJsonIO::SetValue(root, listen, "listen"); QJsonIO::SetValue(root, listen, "listen");
QJsonIO::SetValue(root, port, "port"); QJsonIO::SetValue(root, defaultPort, "port");
QJsonIO::SetValue(root, protocol, "protocol"); QJsonIO::SetValue(root, protocol, "protocol");
QJsonIO::SetValue(root, true, "settings", "udp"); QJsonIO::SetValue(root, true, "settings", "udp");
return root; return root;
} }
InboundCredentials GetInboundCredentials(const QJsonObject &xrayConfig)
{
InboundCredentials creds;
creds.port = defaultPort;
const QJsonArray inbounds = xrayConfig.value("inbounds").toArray();
const int socksIdx = indexOfSocksInbound(inbounds);
if (socksIdx < 0)
return creds;
const QJsonObject inbound = inbounds.at(socksIdx).toObject();
creds.port = inbound.value("port").toInt(defaultPort);
const QJsonObject settings = inbound.value("settings").toObject();
const QJsonArray accounts = settings.value("accounts").toArray();
if (accounts.isEmpty())
return creds;
const QJsonObject account = accounts.first().toObject();
creds.username = account.value("user").toString();
creds.password = account.value("pass").toString();
return creds;
}
InboundCredentials EnsureInboundAuth(QJsonObject &xrayConfig)
{
QJsonArray inbounds = xrayConfig.value("inbounds").toArray();
const int socksIdx = indexOfSocksInbound(inbounds);
if (socksIdx < 0)
return GetInboundCredentials(xrayConfig); // no SOCKS inbound to patch
QJsonObject inbound = inbounds.at(socksIdx).toObject();
InboundCredentials creds;
creds.port = acquireFreeLocalPort();
inbound["port"] = creds.port;
QJsonObject settings = inbound.value("settings").toObject();
const QJsonArray accounts = settings.value("accounts").toArray();
if (!accounts.isEmpty()) {
const QJsonObject account = accounts.first().toObject();
creds.username = account.value("user").toString();
creds.password = account.value("pass").toString();
}
if (creds.username.isEmpty() || creds.password.isEmpty()) {
// Generate fresh credentials for this session (never persisted)
creds.username = generateRandomHex(8); // 16 hex chars
creds.password = generateRandomHex(16); // 32 hex chars
QJsonObject account;
account["user"] = creds.username;
account["pass"] = creds.password;
settings["accounts"] = QJsonArray{ account };
}
// Always ensure auth mode is enforced, even for imported configs that had
// accounts but auth: "noauth" (or no auth field at all).
settings["auth"] = QStringLiteral("password");
inbound["settings"] = settings;
inbounds[socksIdx] = inbound;
xrayConfig["inbounds"] = inbounds;
return creds;
}
} // namespace amnezia::serialization::inbounds } // namespace amnezia::serialization::inbounds

View File

@@ -60,7 +60,24 @@ namespace amnezia::serialization
namespace inbounds namespace inbounds
{ {
struct InboundCredentials {
QString username;
QString password;
int port;
};
QJsonObject GenerateInboundEntry(); QJsonObject GenerateInboundEntry();
// Reads existing SOCKS5 auth from the first inbound with protocol "socks"
// (.settings.accounts[0]). Returns empty username/password if none.
InboundCredentials GetInboundCredentials(const QJsonObject &xrayConfig);
// Ensures SOCKS5 auth is present on the inbound whose protocol is "socks".
// Re-uses existing credentials if already set; otherwise generates random ones
// and writes them into the config. Assigns a free loopback TCP port each session
// (OS-assigned). Throws std::runtime_error if a SOCKS inbound exists but binding
// a local port on 127.0.0.1 fails (e.g. permissions or OS error).
InboundCredentials EnsureInboundAuth(QJsonObject &xrayConfig);
} }
} }

View File

@@ -1,3 +1,4 @@
import Darwin
import Foundation import Foundation
import NetworkExtension import NetworkExtension
@@ -6,6 +7,7 @@ enum XrayErrors: Error {
case xrayConfigIsWrong case xrayConfigIsWrong
case cantSaveXrayConfig case cantSaveXrayConfig
case cantParseListenAndPort case cantParseListenAndPort
case cantAcquireLocalPort
case cantSaveHevSocksConfig case cantSaveHevSocksConfig
} }
@@ -21,6 +23,42 @@ extension Constants {
} }
extension PacketTunnelProvider { extension PacketTunnelProvider {
/// TCP port chosen by the OS on IPv6 loopback (::1), matching inbound listen address.
private func acquireFreeLocalPort() throws -> Int {
let fd = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP)
guard fd != -1 else {
throw XrayErrors.cantAcquireLocalPort
}
defer { close(fd) }
var reuse: Int32 = 1
_ = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, socklen_t(MemoryLayout<Int32>.size))
var addr = sockaddr_in6()
addr.sin6_len = UInt8(MemoryLayout<sockaddr_in6>.size)
addr.sin6_family = sa_family_t(AF_INET6)
addr.sin6_port = in_port_t(0).bigEndian
addr.sin6_addr = in6addr_loopback
addr.sin6_scope_id = 0
let bindResult = withUnsafePointer(to: &addr) { ptr in
ptr.withMemoryRebound(to: sockaddr.self, capacity: 1) { p in
bind(fd, p, socklen_t(MemoryLayout<sockaddr_in6>.size))
}
}
guard bindResult == 0 else {
throw XrayErrors.cantAcquireLocalPort
}
var bound = sockaddr_in6()
var len = socklen_t(MemoryLayout<sockaddr_in6>.size)
let gr = withUnsafeMutablePointer(to: &bound) { p in
p.withMemoryRebound(to: sockaddr.self, capacity: 1) { bp in
getsockname(fd, bp, &len)
}
}
guard gr == 0 else {
throw XrayErrors.cantAcquireLocalPort
}
return Int(bound.sin6_port.byteSwapped)
}
private func applyXraySplitTunnel(_ xrayConfig: XrayConfig, private func applyXraySplitTunnel(_ xrayConfig: XrayConfig,
settings: NEPacketTunnelNetworkSettings) { settings: NEPacketTunnelNetworkSettings) {
guard let splitTunnelType = xrayConfig.splitTunnelType else { guard let splitTunnelType = xrayConfig.splitTunnelType else {
@@ -129,14 +167,11 @@ extension PacketTunnelProvider {
return return
} }
let port = 10808 let port = try acquireFreeLocalPort()
let address = "::1" let address = "::1"
if var inboundsArray = jsonDict["inbounds"] as? [[String: Any]], !inboundsArray.isEmpty { // Extract existing SOCKS5 credentials or generate new ones per session.
inboundsArray[0]["port"] = port let socksCredentials = ensureInboundAuth(jsonDict: &jsonDict, port: port, address: address)
inboundsArray[0]["listen"] = address
jsonDict["inbounds"] = inboundsArray
}
let updatedData = try JSONSerialization.data(withJSONObject: jsonDict, options: []) let updatedData = try JSONSerialization.data(withJSONObject: jsonDict, options: [])
@@ -159,6 +194,8 @@ extension PacketTunnelProvider {
self?.setupAndRunTun2socks(configData: updatedData, self?.setupAndRunTun2socks(configData: updatedData,
address: address, address: address,
port: port, port: port,
username: socksCredentials.username,
password: socksCredentials.password,
completionHandler: completionHandler) completionHandler: completionHandler)
} }
} }
@@ -183,6 +220,62 @@ extension PacketTunnelProvider {
} }
} }
private struct SocksCredentials {
let username: String
let password: String
}
private func indexOfSocksInbound(in inboundsArray: [[String: Any]]) -> Int? {
for (i, inbound) in inboundsArray.enumerated() {
guard let proto = inbound["protocol"] as? String else { continue }
if proto.caseInsensitiveCompare("socks") == .orderedSame {
return i
}
}
return nil
}
// Returns existing SOCKS5 credentials from the inbound config, or generates and injects
// new random ones. Also sets port and address on the socks inbound entry.
private func ensureInboundAuth(jsonDict: inout [String: Any], port: Int, address: String) -> SocksCredentials {
var inboundsArray = jsonDict["inbounds"] as? [[String: Any]] ?? []
if let socksIdx = indexOfSocksInbound(in: inboundsArray) {
var inbound = inboundsArray[socksIdx]
inbound["port"] = port
inbound["listen"] = address
var settings = inbound["settings"] as? [String: Any] ?? [:]
if let accounts = settings["accounts"] as? [[String: Any]],
let first = accounts.first,
let user = first["user"] as? String, !user.isEmpty,
let pass = first["pass"] as? String, !pass.isEmpty {
// Re-use existing credentials, but always enforce auth mode in case the
// imported config had accounts but auth: "noauth" (or no auth field).
settings["auth"] = "password"
inbound["settings"] = settings
inboundsArray[socksIdx] = inbound
jsonDict["inbounds"] = inboundsArray
return SocksCredentials(username: user, password: pass)
}
// Generate new random credentials for this session
let user = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased().prefix(16)
let pass = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased()
settings["auth"] = "password"
settings["accounts"] = [["user": String(user), "pass": pass]]
inbound["settings"] = settings
inboundsArray[socksIdx] = inbound
jsonDict["inbounds"] = inboundsArray
return SocksCredentials(username: String(user), password: pass)
}
// Fallback: no socks inbound generate credentials but can't inject
let user = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased().prefix(16)
let pass = UUID().uuidString.replacingOccurrences(of: "-", with: "").lowercased()
return SocksCredentials(username: String(user), password: pass)
}
private func setupAndStartXray(configData: Data, private func setupAndStartXray(configData: Data,
completionHandler: @escaping (Error?) -> Void) { completionHandler: @escaping (Error?) -> Void) {
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
@@ -214,6 +307,8 @@ extension PacketTunnelProvider {
private func setupAndRunTun2socks(configData: Data, private func setupAndRunTun2socks(configData: Data,
address: String, address: String,
port: Int, port: Int,
username: String,
password: String,
completionHandler: @escaping (Error?) -> Void) { completionHandler: @escaping (Error?) -> Void) {
let config = """ let config = """
tunnel: tunnel:
@@ -221,6 +316,8 @@ extension PacketTunnelProvider {
socks5: socks5:
port: \(port) port: \(port)
address: \(address) address: \(address)
username: \(username)
password: \(password)
udp: 'udp' udp: 'udp'
misc: misc:
task-stack-size: 20480 task-stack-size: 20480

View File

@@ -1,6 +1,7 @@
#include "xrayprotocol.h" #include "xrayprotocol.h"
#include "core/ipcclient.h" #include "core/ipcclient.h"
#include "core/serialization/serialization.h"
#include "ipc.h" #include "ipc.h"
#include "utilities.h" #include "utilities.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
@@ -14,6 +15,8 @@
#include <QtCore/qobjectdefs.h> #include <QtCore/qobjectdefs.h>
#include <QtCore/qprocess.h> #include <QtCore/qprocess.h>
#include <exception>
#ifdef Q_OS_MACOS #ifdef Q_OS_MACOS
static const QString tunName = "utun22"; static const QString tunName = "utun22";
#else #else
@@ -53,6 +56,19 @@ ErrorCode XrayProtocol::start()
{ {
qDebug() << "XrayProtocol::start()"; qDebug() << "XrayProtocol::start()";
// Inject SOCKS5 auth into the inbound before starting xray.
// Re-uses existing credentials if the config already has them (e.g. imported config).
amnezia::serialization::inbounds::InboundCredentials creds;
try {
creds = amnezia::serialization::inbounds::EnsureInboundAuth(m_xrayConfig);
} catch (const std::exception &e) {
qCritical() << "EnsureInboundAuth failed:" << e.what();
return ErrorCode::InternalError;
}
m_socksUser = creds.username;
m_socksPassword = creds.password;
m_socksPort = creds.port;
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) { return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson()); auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) { if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
@@ -121,8 +137,11 @@ ErrorCode XrayProtocol::startTun2Socks()
return ErrorCode::AmneziaServiceConnectionFailed; return ErrorCode::AmneziaServiceConnectionFailed;
} }
const QString proxyUrl = QString("socks5://%1:%2@127.0.0.1:%3")
.arg(m_socksUser, m_socksPassword, QString::number(m_socksPort));
m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks); m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", "socks5://127.0.0.1:10808" }); m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", proxyUrl});
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() { connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() {
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput(); auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
@@ -136,7 +155,7 @@ ErrorCode XrayProtocol::startTun2Socks()
if (!line.contains("[TCP]") && !line.contains("[UDP]")) if (!line.contains("[TCP]") && !line.contains("[UDP]"))
qDebug() << "[tun2socks]:" << line; qDebug() << "[tun2socks]:" << line;
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) { if (line.contains("[STACK] tun://") && line.contains("<-> socks5://")) {
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr); disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) { if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {

View File

@@ -26,6 +26,10 @@ private:
QList<QHostAddress> m_dnsServers; QList<QHostAddress> m_dnsServers;
QString m_remoteAddress; QString m_remoteAddress;
QString m_socksUser;
QString m_socksPassword;
int m_socksPort = 10808;
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess; QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
}; };