mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
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:
@@ -4,6 +4,9 @@ import android.content.Context
|
||||
import android.net.VpnService.Builder
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.net.InetAddress
|
||||
import java.net.ServerSocket
|
||||
import java.util.UUID
|
||||
import go.Seq
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
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.ip
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
private const val TAG = "Xray"
|
||||
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() {
|
||||
|
||||
private var isRunning: Boolean = false
|
||||
@@ -56,6 +80,10 @@ class Xray : Protocol() {
|
||||
val xrayJsonConfig = config.optJSONObject("xray_config_data")
|
||||
?: config.optJSONObject("ssxray_config_data")
|
||||
?: 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)
|
||||
|
||||
(xrayJsonConfig.optJSONObject("log") ?: JSONObject().also { xrayJsonConfig.put("log", it) })
|
||||
@@ -97,9 +125,22 @@ class Xray : Protocol() {
|
||||
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) }
|
||||
|
||||
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)
|
||||
configAppSplitTunneling(config)
|
||||
}
|
||||
@@ -162,9 +203,10 @@ class Xray : Protocol() {
|
||||
}
|
||||
|
||||
private fun runTun2Socks(config: XrayConfig, fd: Int) {
|
||||
val proxyUrl = "socks5://${config.socksUser}:${config.socksPass}@127.0.0.1:${config.socksPort}"
|
||||
val tun2SocksConfig = Tun2SocksConfig().apply {
|
||||
mtu = config.mtu.toLong()
|
||||
proxy = "socks5://127.0.0.1:${config.socksPort}"
|
||||
proxy = proxyUrl
|
||||
device = "fd://$fd"
|
||||
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 {
|
||||
val instance: Xray by lazy { Xray() }
|
||||
}
|
||||
|
||||
@@ -9,12 +9,16 @@ private const val XRAY_DEFAULT_MAX_MEMORY: Long = 50 shl 20 // 50 MB
|
||||
class XrayConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val socksPort: Int,
|
||||
val socksUser: String,
|
||||
val socksPass: String,
|
||||
val maxMemory: Long,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
builder,
|
||||
builder.socksPort,
|
||||
builder.socksUser,
|
||||
builder.socksPass,
|
||||
builder.maxMemory
|
||||
)
|
||||
|
||||
@@ -22,6 +26,12 @@ class XrayConfig protected constructor(
|
||||
internal var socksPort: Int = 0
|
||||
private set
|
||||
|
||||
internal var socksUser: String = ""
|
||||
private set
|
||||
|
||||
internal var socksPass: String = ""
|
||||
private set
|
||||
|
||||
internal var maxMemory: Long = XRAY_DEFAULT_MAX_MEMORY
|
||||
private set
|
||||
|
||||
@@ -29,6 +39,10 @@ class XrayConfig protected constructor(
|
||||
|
||||
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 }
|
||||
|
||||
override fun build(): XrayConfig = configBuild().run { XrayConfig(this@Builder) }
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
#include <QString>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QList>
|
||||
#include <QHostAddress>
|
||||
#include <QRandomGenerator>
|
||||
#include <QTcpServer>
|
||||
#include <stdexcept>
|
||||
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||
#include "transfer.h"
|
||||
#include "serialization.h"
|
||||
@@ -14,25 +19,125 @@ namespace amnezia::serialization::inbounds
|
||||
// "port": 10808,
|
||||
// "protocol": "socks",
|
||||
// "settings": {
|
||||
// "auth": "password",
|
||||
// "accounts": [{"user": "...", "pass": "..."}],
|
||||
// "udp": true
|
||||
// }
|
||||
// }
|
||||
//],
|
||||
|
||||
const static QString listen = "127.0.0.1";
|
||||
const static int port = 10808;
|
||||
const static int defaultPort = 10808;
|
||||
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 root;
|
||||
QJsonIO::SetValue(root, listen, "listen");
|
||||
QJsonIO::SetValue(root, port, "port");
|
||||
QJsonIO::SetValue(root, defaultPort, "port");
|
||||
QJsonIO::SetValue(root, protocol, "protocol");
|
||||
QJsonIO::SetValue(root, true, "settings", "udp");
|
||||
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
|
||||
|
||||
|
||||
@@ -60,7 +60,24 @@ namespace amnezia::serialization
|
||||
|
||||
namespace inbounds
|
||||
{
|
||||
struct InboundCredentials {
|
||||
QString username;
|
||||
QString password;
|
||||
int port;
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import Darwin
|
||||
import Foundation
|
||||
import NetworkExtension
|
||||
|
||||
@@ -6,6 +7,7 @@ enum XrayErrors: Error {
|
||||
case xrayConfigIsWrong
|
||||
case cantSaveXrayConfig
|
||||
case cantParseListenAndPort
|
||||
case cantAcquireLocalPort
|
||||
case cantSaveHevSocksConfig
|
||||
}
|
||||
|
||||
@@ -21,6 +23,42 @@ extension Constants {
|
||||
}
|
||||
|
||||
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,
|
||||
settings: NEPacketTunnelNetworkSettings) {
|
||||
guard let splitTunnelType = xrayConfig.splitTunnelType else {
|
||||
@@ -129,14 +167,11 @@ extension PacketTunnelProvider {
|
||||
return
|
||||
}
|
||||
|
||||
let port = 10808
|
||||
let port = try acquireFreeLocalPort()
|
||||
let address = "::1"
|
||||
|
||||
if var inboundsArray = jsonDict["inbounds"] as? [[String: Any]], !inboundsArray.isEmpty {
|
||||
inboundsArray[0]["port"] = port
|
||||
inboundsArray[0]["listen"] = address
|
||||
jsonDict["inbounds"] = inboundsArray
|
||||
}
|
||||
// Extract existing SOCKS5 credentials or generate new ones per session.
|
||||
let socksCredentials = ensureInboundAuth(jsonDict: &jsonDict, port: port, address: address)
|
||||
|
||||
let updatedData = try JSONSerialization.data(withJSONObject: jsonDict, options: [])
|
||||
|
||||
@@ -159,6 +194,8 @@ extension PacketTunnelProvider {
|
||||
self?.setupAndRunTun2socks(configData: updatedData,
|
||||
address: address,
|
||||
port: port,
|
||||
username: socksCredentials.username,
|
||||
password: socksCredentials.password,
|
||||
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,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
let path = Constants.cachesDirectory.appendingPathComponent("config.json", isDirectory: false).path
|
||||
@@ -214,6 +307,8 @@ extension PacketTunnelProvider {
|
||||
private func setupAndRunTun2socks(configData: Data,
|
||||
address: String,
|
||||
port: Int,
|
||||
username: String,
|
||||
password: String,
|
||||
completionHandler: @escaping (Error?) -> Void) {
|
||||
let config = """
|
||||
tunnel:
|
||||
@@ -221,6 +316,8 @@ extension PacketTunnelProvider {
|
||||
socks5:
|
||||
port: \(port)
|
||||
address: \(address)
|
||||
username: \(username)
|
||||
password: \(password)
|
||||
udp: 'udp'
|
||||
misc:
|
||||
task-stack-size: 20480
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "xrayprotocol.h"
|
||||
|
||||
#include "core/ipcclient.h"
|
||||
#include "core/serialization/serialization.h"
|
||||
#include "ipc.h"
|
||||
#include "utilities.h"
|
||||
#include "core/networkUtilities.h"
|
||||
@@ -14,6 +15,8 @@
|
||||
#include <QtCore/qobjectdefs.h>
|
||||
#include <QtCore/qprocess.h>
|
||||
|
||||
#include <exception>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
static const QString tunName = "utun22";
|
||||
#else
|
||||
@@ -53,6 +56,19 @@ ErrorCode 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) {
|
||||
auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
|
||||
if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
|
||||
@@ -121,8 +137,11 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
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->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]() {
|
||||
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
|
||||
@@ -136,7 +155,7 @@ ErrorCode XrayProtocol::startTun2Socks()
|
||||
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
|
||||
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);
|
||||
|
||||
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
|
||||
|
||||
@@ -26,6 +26,10 @@ private:
|
||||
QList<QHostAddress> m_dnsServers;
|
||||
QString m_remoteAddress;
|
||||
|
||||
QString m_socksUser;
|
||||
QString m_socksPassword;
|
||||
int m_socksPort = 10808;
|
||||
|
||||
QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user