mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-25 03:27:55 +03:00
Compare commits
4 Commits
fix/valnur
...
feat/suppo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f9131af4ad | ||
|
|
423648a280 | ||
|
|
a356b25fa5 | ||
|
|
299c3bab1d |
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@@ -157,7 +157,7 @@ jobs:
|
||||
run: pip install "conan==2.28.0"
|
||||
|
||||
- name: 'Build dependencies'
|
||||
run: cmake -S . -B build -DPREBUILTS_ONLY=1
|
||||
run: cmake -S . -B build -G "Visual Studio 17 2022" -DPREBUILTS_ONLY=1
|
||||
|
||||
- name: 'Authorize in remote'
|
||||
if: github.ref == 'refs/heads/dev'
|
||||
|
||||
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.2)
|
||||
set(AMNEZIAVPN_VERSION 4.9.0.1)
|
||||
|
||||
set(QT_CREATOR_SKIP_PACKAGE_MANAGER_SETUP ON CACHE BOOL "" FORCE)
|
||||
set(CMAKE_PROJECT_TOP_LEVEL_INCLUDES
|
||||
@@ -28,7 +28,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 2123)
|
||||
set(APP_ANDROID_VERSION_CODE 2122)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -119,13 +119,7 @@ void AmneziaApplication::init()
|
||||
win->setPersistentSceneGraph(true);
|
||||
win->setPersistentGraphics(true);
|
||||
#endif
|
||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||
win->show();
|
||||
#else
|
||||
if (!m_coreController || !m_coreController->pageController()->shouldStartMinimized()) {
|
||||
win->show();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<gradient
|
||||
android:type="linear"
|
||||
android:angle="135"
|
||||
android:startColor="#2A2A2E"
|
||||
android:centerColor="#17171A"
|
||||
android:endColor="#0E0E11" />
|
||||
</shape>
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<inset xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:drawable="@drawable/ic_amnezia_round"
|
||||
android:insetLeft="19.5%"
|
||||
android:insetTop="19.5%"
|
||||
android:insetRight="19.5%"
|
||||
android:insetBottom="19.5%" />
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome" />
|
||||
</adaptive-icon>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 6.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 35 KiB |
@@ -1,16 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="NoActionBar">
|
||||
<item name="android:windowBackground">@color/black</item>
|
||||
<item name="android:colorBackground">@color/black</item>
|
||||
<item name="android:windowActionBar">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:enforceNavigationBarContrast">false</item>
|
||||
<item name="android:enforceStatusBarContrast">false</item>
|
||||
|
||||
<item name="android:windowSplashScreenBackground">@color/ic_launcher_background</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">@color/ic_launcher_background</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@mipmap/icon</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -1,4 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#0E0E11</color>
|
||||
</resources>
|
||||
@@ -88,33 +88,68 @@ open class Wireguard : Protocol() {
|
||||
addDnsServer(parseInetAddress(dns.trim()))
|
||||
}
|
||||
|
||||
val defRoutes = hashSetOf(
|
||||
InetNetwork("0.0.0.0", 0),
|
||||
InetNetwork("::", 0)
|
||||
)
|
||||
val routes = hashSetOf<InetNetwork>()
|
||||
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||
InetNetwork.parse(route.trim())
|
||||
}.forEach(routes::add)
|
||||
// if the allowed IPs list contains at least one non-default route, disable global split tunneling
|
||||
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
||||
addRoutes(routes)
|
||||
|
||||
configData.optStringOrNull("mtu")?.let { setMtu(it.toInt()) }
|
||||
|
||||
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||
val port = configData.getInt("port")
|
||||
setEndpoint(InetEndpoint(host, port))
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
|
||||
if (configData.optBoolean("isObfuscationEnabled")) {
|
||||
setUseProtocolExtension(true)
|
||||
configExtensionParameters(configData)
|
||||
}
|
||||
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("client_priv_key").let { setPrivateKeyHex(it.base64ToHex()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
val defRoutes = hashSetOf(InetNetwork("0.0.0.0", 0), InetNetwork("::", 0))
|
||||
val peersArray = configData.optJSONArray("peers")
|
||||
|
||||
if (peersArray != null && peersArray.length() > 0) {
|
||||
// Multi-peer: collect union of all peers' allowed IPs for the VPN interface routing table
|
||||
val allRoutes = hashSetOf<InetNetwork>()
|
||||
for (i in 0 until peersArray.length()) {
|
||||
peersArray.getJSONObject(i).getJSONArray("allowed_ips").asSequence<String>()
|
||||
.map { InetNetwork.parse(it.trim()) }.forEach(allRoutes::add)
|
||||
}
|
||||
if (allRoutes.any { it !in defRoutes }) disableSplitTunneling()
|
||||
addRoutes(allRoutes)
|
||||
|
||||
// Primary peer from first entry
|
||||
val firstPeer = peersArray.getJSONObject(0)
|
||||
val firstAllowedIps = firstPeer.getJSONArray("allowed_ips").asSequence<String>()
|
||||
.map { InetNetwork.parse(it.trim()) }.toList()
|
||||
setPeerAllowedIps(firstAllowedIps)
|
||||
setEndpoint(InetEndpoint(parseInetAddress(firstPeer.getString("hostName").trim()), firstPeer.getInt("port")))
|
||||
firstPeer.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
firstPeer.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
firstPeer.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
|
||||
// Additional peers
|
||||
for (i in 1 until peersArray.length()) {
|
||||
val peerData = peersArray.getJSONObject(i)
|
||||
val peerAllowedIps = peerData.getJSONArray("allowed_ips").asSequence<String>()
|
||||
.map { InetNetwork.parse(it.trim()) }.toList()
|
||||
addPeer(
|
||||
PeerConfig(
|
||||
publicKeyHex = peerData.getString("server_pub_key").base64ToHex(),
|
||||
preSharedKeyHex = peerData.optStringOrNull("psk_key")?.base64ToHex(),
|
||||
persistentKeepalive = peerData.optStringOrNull("persistent_keep_alive")?.toInt() ?: 0,
|
||||
endpoint = InetEndpoint(parseInetAddress(peerData.getString("hostName").trim()), peerData.getInt("port")),
|
||||
allowedIps = peerAllowedIps
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// Single peer (original behavior)
|
||||
val routes = hashSetOf<InetNetwork>()
|
||||
configData.getJSONArray("allowed_ips").asSequence<String>().map { route ->
|
||||
InetNetwork.parse(route.trim())
|
||||
}.forEach(routes::add)
|
||||
if (routes.any { it !in defRoutes }) disableSplitTunneling()
|
||||
addRoutes(routes)
|
||||
|
||||
val host = configData.getString("hostName").let { parseInetAddress(it.trim()) }
|
||||
val port = configData.getInt("port")
|
||||
setEndpoint(InetEndpoint(host, port))
|
||||
configData.optStringOrNull("persistent_keep_alive")?.let { setPersistentKeepalive(it.toInt()) }
|
||||
configData.getString("server_pub_key").let { setPublicKeyHex(it.base64ToHex()) }
|
||||
configData.optStringOrNull("psk_key")?.let { setPreSharedKeyHex(it.base64ToHex()) }
|
||||
}
|
||||
}
|
||||
|
||||
protected fun WireguardConfig.Builder.configExtensionParameters(configData: JSONObject) {
|
||||
@@ -201,7 +236,11 @@ open class Wireguard : Protocol() {
|
||||
Log.e(TAG, "Failed to get tunnel config")
|
||||
return -2
|
||||
}
|
||||
val lastHandshake = config.lines().find { it.startsWith("last_handshake_time_sec=") }?.substring(24)?.toLong()
|
||||
// For multi-peer: take the max handshake time across all peers (any connected peer = tunnel active)
|
||||
val lastHandshake = config.lines()
|
||||
.filter { it.startsWith("last_handshake_time_sec=") }
|
||||
.mapNotNull { it.substring(24).toLongOrNull() }
|
||||
.maxOrNull()
|
||||
if (lastHandshake == null) {
|
||||
Log.e(TAG, "Failed to get last_handshake_time_sec")
|
||||
return -2
|
||||
|
||||
@@ -4,9 +4,18 @@ import android.util.Base64
|
||||
import org.amnezia.vpn.protocol.BadConfigException
|
||||
import org.amnezia.vpn.protocol.ProtocolConfig
|
||||
import org.amnezia.vpn.util.net.InetEndpoint
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
|
||||
private const val WIREGUARD_DEFAULT_MTU = 1280
|
||||
|
||||
data class PeerConfig(
|
||||
val publicKeyHex: String,
|
||||
val preSharedKeyHex: String?,
|
||||
val persistentKeepalive: Int,
|
||||
val endpoint: InetEndpoint,
|
||||
val allowedIps: List<InetNetwork>
|
||||
)
|
||||
|
||||
open class WireguardConfig protected constructor(
|
||||
protocolConfigBuilder: ProtocolConfig.Builder,
|
||||
val endpoint: InetEndpoint,
|
||||
@@ -31,6 +40,8 @@ open class WireguardConfig protected constructor(
|
||||
var i3: String?,
|
||||
var i4: String?,
|
||||
var i5: String?,
|
||||
val peerAllowedIps: List<InetNetwork>?,
|
||||
val additionalPeers: List<PeerConfig>,
|
||||
) : ProtocolConfig(protocolConfigBuilder) {
|
||||
|
||||
protected constructor(builder: Builder) : this(
|
||||
@@ -57,6 +68,8 @@ open class WireguardConfig protected constructor(
|
||||
builder.i3,
|
||||
builder.i4,
|
||||
builder.i5,
|
||||
builder.peerAllowedIps,
|
||||
builder.additionalPeers.toList(),
|
||||
)
|
||||
|
||||
fun toWgUserspaceString(): String = with(StringBuilder()) {
|
||||
@@ -103,14 +116,22 @@ open class WireguardConfig protected constructor(
|
||||
|
||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||
appendLine("public_key=$publicKeyHex")
|
||||
routes.filter { it.include }.forEach { route ->
|
||||
appendLine("allowed_ip=${route.inetNetwork}")
|
||||
}
|
||||
val primaryIps = peerAllowedIps ?: routes.filter { it.include }.map { it.inetNetwork }
|
||||
primaryIps.forEach { net -> appendLine("allowed_ip=$net") }
|
||||
appendLine("endpoint=$endpoint")
|
||||
if (persistentKeepalive != 0)
|
||||
appendLine("persistent_keepalive_interval=$persistentKeepalive")
|
||||
if (preSharedKeyHex != null)
|
||||
appendLine("preshared_key=$preSharedKeyHex")
|
||||
for (peer in additionalPeers) {
|
||||
appendLine("public_key=${peer.publicKeyHex}")
|
||||
peer.allowedIps.forEach { net -> appendLine("allowed_ip=$net") }
|
||||
appendLine("endpoint=${peer.endpoint}")
|
||||
if (peer.persistentKeepalive != 0)
|
||||
appendLine("persistent_keepalive_interval=${peer.persistentKeepalive}")
|
||||
if (peer.preSharedKeyHex != null)
|
||||
appendLine("preshared_key=${peer.preSharedKeyHex}")
|
||||
}
|
||||
}
|
||||
|
||||
open class Builder : ProtocolConfig.Builder(true) {
|
||||
@@ -150,6 +171,9 @@ open class WireguardConfig protected constructor(
|
||||
internal var i4: String? = null
|
||||
internal var i5: String? = null
|
||||
|
||||
internal var peerAllowedIps: List<InetNetwork>? = null
|
||||
internal val additionalPeers: MutableList<PeerConfig> = mutableListOf()
|
||||
|
||||
fun setEndpoint(endpoint: InetEndpoint) = apply { this.endpoint = endpoint }
|
||||
|
||||
fun setPersistentKeepalive(persistentKeepalive: Int) = apply { this.persistentKeepalive = persistentKeepalive }
|
||||
@@ -179,6 +203,9 @@ open class WireguardConfig protected constructor(
|
||||
fun setI4(i4: String) = apply { this.i4 = i4 }
|
||||
fun setI5(i5: String) = apply { this.i5 = i5 }
|
||||
|
||||
fun setPeerAllowedIps(ips: List<InetNetwork>) = apply { this.peerAllowedIps = ips }
|
||||
fun addPeer(peer: PeerConfig) = apply { this.additionalPeers += peer }
|
||||
|
||||
override fun build(): WireguardConfig = configBuild().run { WireguardConfig(this@Builder) }
|
||||
}
|
||||
|
||||
|
||||
@@ -152,5 +152,5 @@ message(${QtCore_location})
|
||||
get_filename_component(QT_BIN_DIR_DETECTED "${QtCore_location}/../../../../../bin" ABSOLUTE)
|
||||
|
||||
add_custom_command(TARGET ${PROJECT} POST_BUILD
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR} -no-codesign
|
||||
COMMAND ${QT_BIN_DIR_DETECTED}/macdeployqt $<TARGET_BUNDLE_DIR:AmneziaVPN> -appstore-compliant -qmldir=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
@@ -244,7 +244,11 @@ ErrorCode XrayConfigurator::applyServerSettingsToRemote(const ServerCredentials
|
||||
<< "container=" << static_cast<int>(container) << "host=" << credentials.hostName
|
||||
<< "transport=" << srv.transport << "security=" << srv.security << "port=" << srv.port
|
||||
<< "appendClient=" << appendNewClient;
|
||||
const QString flowValue = srv.flow;
|
||||
QString flowValue = srv.flow;
|
||||
if (flowValue.isEmpty() && srv.security == QLatin1String("reality")) {
|
||||
flowValue = QStringLiteral("xtls-rprx-vision");
|
||||
}
|
||||
|
||||
QString realityPublicKey;
|
||||
QString realityShortId;
|
||||
if (srv.security == QLatin1String("reality")) {
|
||||
@@ -559,12 +563,9 @@ QJsonObject XrayConfigurator::buildStreamSettings(const XrayServerConfig &srv, c
|
||||
if (pad.obfsMode) {
|
||||
if (!pad.bytesMin.isEmpty() || !pad.bytesMax.isEmpty()) {
|
||||
QJsonObject br;
|
||||
const int fromV = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
|
||||
int toV = pad.bytesMax.isEmpty() ? 256 : pad.bytesMax.toInt();
|
||||
if (toV < fromV)
|
||||
toV = fromV;
|
||||
br[QStringLiteral("from")] = fromV;
|
||||
br[QStringLiteral("to")] = toV;
|
||||
br[QStringLiteral("from")] = pad.bytesMin.isEmpty() ? 1 : pad.bytesMin.toInt();
|
||||
br[QStringLiteral("to")] = pad.bytesMax.isEmpty() ? (pad.bytesMin.isEmpty() ? 256 : pad.bytesMin.toInt())
|
||||
: pad.bytesMax.toInt();
|
||||
xo[QStringLiteral("xPaddingBytes")] = br;
|
||||
}
|
||||
xo[QStringLiteral("xPaddingKey")] = pad.key.isEmpty() ? QStringLiteral("x_padding") : pad.key;
|
||||
|
||||
@@ -49,96 +49,14 @@ void ConnectionController::setConnectionState(Vpn::ConnectionState state)
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::defaultContainerForServer(const QString &serverId, DockerContainer &container) const
|
||||
{
|
||||
const auto kind = m_serversRepository->serverKind(serverId);
|
||||
switch (kind) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
const auto cfg = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
const auto cfg = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
const auto cfg = m_serversRepository->nativeConfig(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV2:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV3:
|
||||
case serverConfigUtils::ConfigType::ExternalPremium: {
|
||||
const auto cfg = m_serversRepository->apiV2Config(serverId);
|
||||
if (!cfg.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
container = cfg->defaultContainer;
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::AmneziaPremiumV1:
|
||||
case serverConfigUtils::ConfigType::AmneziaFreeV2:
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
case serverConfigUtils::ConfigType::Invalid:
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::isConnectionSupported(const QString &serverId) const
|
||||
{
|
||||
if (serverId.isEmpty()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
const serverConfigUtils::ConfigType kind = m_serversRepository->serverKind(serverId);
|
||||
if (serverConfigUtils::isLegacyApiSubscription(kind)) {
|
||||
return ErrorCode::LegacyApiV1NotSupportedError;
|
||||
}
|
||||
|
||||
DockerContainer container = DockerContainer::None;
|
||||
const ErrorCode errorCode = defaultContainerForServer(serverId, container);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
if (container == DockerContainer::None) {
|
||||
if (serverConfigUtils::isApiV2Subscription(kind)) {
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
return ErrorCode::NoInstalledContainersError;
|
||||
}
|
||||
|
||||
if (ContainerUtils::isUnsupportedContainer(container)) {
|
||||
return ErrorCode::LegacyContainerNotSupportedError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
|
||||
ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container)
|
||||
{
|
||||
if (!isServiceReady()) {
|
||||
return ErrorCode::AmneziaServiceNotRunning;
|
||||
}
|
||||
|
||||
ContainerConfig containerConfigModel;
|
||||
QPair<QString, QString> dns;
|
||||
QString hostName;
|
||||
@@ -202,6 +120,10 @@ ErrorCode ConnectionController::prepareConnection(const QString &serverId,
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
|
||||
if (!isContainerSupported(container)) {
|
||||
return ErrorCode::NotSupportedOnThisPlatform;
|
||||
}
|
||||
|
||||
vpnConfiguration = createConnectionConfiguration(dns, isApiConfig, hostName, description, configVersion,
|
||||
containerConfigModel, container);
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ public:
|
||||
QJsonObject& vpnConfiguration,
|
||||
DockerContainer& container);
|
||||
|
||||
ErrorCode isConnectionSupported(const QString &serverId) const;
|
||||
|
||||
ErrorCode openConnection(const QString &serverId);
|
||||
|
||||
void closeConnection();
|
||||
@@ -75,8 +73,6 @@ signals:
|
||||
#endif
|
||||
|
||||
private:
|
||||
ErrorCode defaultContainerForServer(const QString &serverId, DockerContainer &container) const;
|
||||
|
||||
SecureServersRepository* m_serversRepository;
|
||||
SecureAppSettingsRepository* m_appSettingsRepository;
|
||||
VpnConnection* m_vpnConnection;
|
||||
|
||||
@@ -191,7 +191,7 @@ void CoreController::initControllers()
|
||||
m_languageUiController = new LanguageUiController(m_settingsController, m_languageModel, this);
|
||||
setQmlContextProperty("LanguageUiController", m_languageUiController);
|
||||
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, this);
|
||||
m_settingsUiController = new SettingsUiController(m_settingsController, m_serversController, m_languageUiController, this);
|
||||
setQmlContextProperty("SettingsController", m_settingsUiController);
|
||||
|
||||
m_pageController = new PageController(m_serversController, m_settingsController, this);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "coreSignalHandlers.h"
|
||||
|
||||
#include <QTimer>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "core/utils/selfhosted/sshSession.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
@@ -34,6 +33,7 @@
|
||||
#include "core/controllers/connectionController.h"
|
||||
#include "ui/models/clientManagementModel.h"
|
||||
#include "ui/controllers/api/apiNewsUiController.h"
|
||||
#include "ui/models/api/apiCountryModel.h"
|
||||
#include "ui/models/containersModel.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
@@ -145,9 +145,7 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::revokeClientRequested, this,
|
||||
[this](const QString &serverId, int row, DockerContainer container) {
|
||||
QtConcurrent::run([this, serverId, row, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
m_coreController->m_usersController->revokeClient(serverId, row, container);
|
||||
});
|
||||
connect(m_coreController->m_exportController, &ExportController::renameClientRequested, this,
|
||||
[this](const QString &serverId, int row, const QString &clientName, DockerContainer container) {
|
||||
@@ -158,17 +156,15 @@ void CoreSignalHandlers::initExportControllerHandler()
|
||||
void CoreSignalHandlers::initImportControllerHandler()
|
||||
{
|
||||
connect(m_coreController->m_importCoreController, &ImportController::importFinished, this, [this]() {
|
||||
if (m_coreController->m_connectionUiController->isConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
if (!m_coreController->m_connectionController->isConnected()) {
|
||||
int newServerIndex = m_coreController->m_serversController->getServersCount() - 1;
|
||||
const QString serverId = m_coreController->m_serversController->getServerId(newServerIndex);
|
||||
if (!serverId.isEmpty()) {
|
||||
m_coreController->m_serversController->setDefaultServer(serverId);
|
||||
}
|
||||
if (m_coreController->m_serversUiController) {
|
||||
m_coreController->m_serversUiController->setProcessedServerId(serverId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -180,14 +176,17 @@ void CoreSignalHandlers::initApiCountryModelUpdateHandler()
|
||||
if (processedServerId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray availableCountries;
|
||||
QString serverCountryCode;
|
||||
|
||||
const auto apiV2 = m_coreController->m_serversRepository->apiV2Config(processedServerId);
|
||||
if (!apiV2.has_value()) {
|
||||
return;
|
||||
if (apiV2.has_value()) {
|
||||
availableCountries = apiV2->apiConfig.availableCountries;
|
||||
serverCountryCode = apiV2->apiConfig.serverCountryCode;
|
||||
}
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(apiV2->apiConfig.availableCountries,
|
||||
apiV2->apiConfig.serverCountryCode);
|
||||
|
||||
m_coreController->m_apiCountryModel->updateModel(availableCountries, serverCountryCode);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -205,15 +204,13 @@ void CoreSignalHandlers::initAdminConfigRevokedHandler()
|
||||
{
|
||||
connect(m_coreController->m_installController, &InstallController::clientRevocationRequested, this,
|
||||
[this](const QString &serverId, const ContainerConfig &containerConfig, DockerContainer container) {
|
||||
QtConcurrent::run([this, serverId, containerConfig, container]() {
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
m_coreController->m_usersController->revokeClient(serverId, containerConfig, container);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_installController, &InstallController::clientAppendRequested, this,
|
||||
[this](const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container) {
|
||||
m_coreController->m_usersController->appendClient(serverId, clientId, clientName, container);
|
||||
}, Qt::DirectConnection);
|
||||
});
|
||||
|
||||
connect(m_coreController->m_usersController, &UsersController::adminConfigRevoked, m_coreController->m_installController,
|
||||
&InstallController::clearCachedProfile);
|
||||
@@ -240,16 +237,13 @@ void CoreSignalHandlers::initLanguageHandler()
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::resetLanguageToSystem, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->changeLanguage(m_coreController->m_languageUiController->getSystemLanguageEnum());
|
||||
});
|
||||
connect(m_coreController->m_settingsUiController, &SettingsUiController::appLanguageChanged, m_coreController->m_languageUiController, [this]() {
|
||||
m_coreController->m_languageUiController->onAppLanguageChanged(m_coreController->m_settingsController->getAppLanguage());
|
||||
});
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initAutoConnectHandler()
|
||||
{
|
||||
if (m_coreController->m_settingsUiController->isAutoConnectEnabled()
|
||||
&& !m_coreController->m_serversController->getDefaultServerId().isEmpty()) {
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->toggleConnection(); });
|
||||
QTimer::singleShot(1000, this, [this]() { m_coreController->m_connectionUiController->openConnection(); });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,8 +284,6 @@ void CoreSignalHandlers::initClientManagementModelUpdateHandler()
|
||||
m_coreController->m_clientManagementModel, &ClientManagementModel::updateModel);
|
||||
connect(m_coreController->m_usersController, &UsersController::clientRenamed,
|
||||
m_coreController->m_clientManagementModel, &ClientManagementModel::updateClientName);
|
||||
connect(m_coreController->m_usersController, &UsersController::revokeFinished,
|
||||
m_coreController->m_exportController, &ExportController::revokeFinished);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initSitesModelUpdateHandler()
|
||||
@@ -356,9 +348,6 @@ void CoreSignalHandlers::initUnsupportedConnectDrawerHandler()
|
||||
{
|
||||
connect(m_coreController->m_subscriptionUiController, &SubscriptionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
|
||||
connect(m_coreController->m_connectionUiController, &ConnectionUiController::unsupportedConnectDrawerRequested,
|
||||
m_coreController->m_pageController, &PageController::unsupportedConnectDrawerRequested);
|
||||
}
|
||||
|
||||
void CoreSignalHandlers::initStrictKillSwitchHandler()
|
||||
|
||||
@@ -48,7 +48,6 @@ signals:
|
||||
void appendClientRequested(const QString &serverId, const QString &clientId, const QString &clientName, DockerContainer container);
|
||||
void updateClientsRequested(const QString &serverId, DockerContainer container);
|
||||
void revokeClientRequested(const QString &serverId, int row, DockerContainer container);
|
||||
void revokeFinished(ErrorCode errorCode);
|
||||
void renameClientRequested(const QString &serverId, int row, const QString &clientName, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -504,24 +504,45 @@ QJsonObject ImportController::extractOpenVpnConfig(const QString &data) const
|
||||
|
||||
QJsonObject ImportController::extractWireGuardConfig(const QString &data, ConfigTypes &configType) const
|
||||
{
|
||||
QMap<QString, QString> configMap;
|
||||
auto configByLines = data.split("\n");
|
||||
QMap<QString, QString> interfaceMap;
|
||||
QList<QMap<QString, QString>> peerList;
|
||||
|
||||
enum class WgSection { None, Interface, Peer };
|
||||
WgSection currentSection = WgSection::None;
|
||||
|
||||
const auto configByLines = data.split("\n");
|
||||
for (const QString &line : configByLines) {
|
||||
QString trimmedLine = line.trimmed();
|
||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
||||
continue;
|
||||
} else {
|
||||
QStringList parts = trimmedLine.split(" = ");
|
||||
const QString trimmedLine = line.trimmed();
|
||||
if (trimmedLine == "[Interface]") {
|
||||
currentSection = WgSection::Interface;
|
||||
} else if (trimmedLine == "[Peer]") {
|
||||
currentSection = WgSection::Peer;
|
||||
peerList.append(QMap<QString, QString>());
|
||||
} else if (!trimmedLine.isEmpty() && !trimmedLine.startsWith("#")) {
|
||||
const QStringList parts = trimmedLine.split(" = ");
|
||||
if (parts.count() == 2) {
|
||||
configMap[parts.at(0).trimmed()] = parts.at(1).trimmed();
|
||||
const QString key = parts.at(0).trimmed();
|
||||
const QString value = parts.at(1).trimmed();
|
||||
if (currentSection == WgSection::Interface) {
|
||||
interfaceMap[key] = value;
|
||||
} else if (currentSection == WgSection::Peer && !peerList.isEmpty()) {
|
||||
peerList.last()[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (peerList.isEmpty()) {
|
||||
qDebug() << "No [Peer] section found in WireGuard config";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
const QMap<QString, QString> &firstPeerMap = peerList.first();
|
||||
|
||||
QJsonObject lastConfig;
|
||||
lastConfig[configKey::config] = data;
|
||||
|
||||
auto url { QUrl::fromUserInput(configMap.value(protocols::wireguard::Endpoint)) };
|
||||
auto url { QUrl::fromUserInput(firstPeerMap.value(protocols::wireguard::Endpoint)) };
|
||||
QString hostName;
|
||||
QString port;
|
||||
if (!url.host().isEmpty()) {
|
||||
@@ -540,37 +561,55 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
lastConfig[configKey::hostName] = hostName;
|
||||
lastConfig[configKey::port] = port.toInt();
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PrivateKey).isEmpty()
|
||||
&& !configMap.value(protocols::wireguard::Address).isEmpty()
|
||||
&& !configMap.value(protocols::wireguard::PublicKey).isEmpty()) {
|
||||
lastConfig[configKey::clientPrivKey] = configMap.value(protocols::wireguard::PrivateKey);
|
||||
lastConfig[configKey::clientIp] = configMap.value(protocols::wireguard::Address);
|
||||
if (!interfaceMap.value(protocols::wireguard::PrivateKey).isEmpty()
|
||||
&& !interfaceMap.value(protocols::wireguard::Address).isEmpty()
|
||||
&& !firstPeerMap.value(protocols::wireguard::PublicKey).isEmpty()) {
|
||||
lastConfig[configKey::clientPrivKey] = interfaceMap.value(protocols::wireguard::PrivateKey);
|
||||
lastConfig[configKey::clientIp] = interfaceMap.value(protocols::wireguard::Address);
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!configMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = configMap.value(protocols::wireguard::PreSharedKey);
|
||||
if (!firstPeerMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = firstPeerMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!firstPeerMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
lastConfig[configKey::pskKey] = firstPeerMap.value(protocols::wireguard::PreSharedKey);
|
||||
}
|
||||
|
||||
lastConfig[configKey::serverPubKey] = configMap.value(protocols::wireguard::PublicKey);
|
||||
lastConfig[configKey::serverPubKey] = firstPeerMap.value(protocols::wireguard::PublicKey);
|
||||
} else {
|
||||
qDebug() << "One of the key parameters is missing (PrivateKey, Address, PublicKey)";
|
||||
return QJsonObject();
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
lastConfig[configKey::persistentKeepAlive] = configMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
if (!firstPeerMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
lastConfig[configKey::persistentKeepAlive] = firstPeerMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
}
|
||||
|
||||
QJsonArray allowedIpsJsonArray = QJsonArray::fromStringList(
|
||||
configMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
firstPeerMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
|
||||
lastConfig[configKey::allowedIps] = allowedIpsJsonArray;
|
||||
|
||||
if (peerList.size() > 1) {
|
||||
QJsonArray peersArray;
|
||||
for (const auto &peerMap : std::as_const(peerList)) {
|
||||
QJsonObject peerObj;
|
||||
const auto peerUrl = QUrl::fromUserInput(peerMap.value(protocols::wireguard::Endpoint));
|
||||
peerObj[configKey::serverPubKey] = peerMap.value(protocols::wireguard::PublicKey);
|
||||
if (!peerMap.value(protocols::wireguard::PresharedKey).isEmpty()) {
|
||||
peerObj[configKey::pskKey] = peerMap.value(protocols::wireguard::PresharedKey);
|
||||
} else if (!peerMap.value(protocols::wireguard::PreSharedKey).isEmpty()) {
|
||||
peerObj[configKey::pskKey] = peerMap.value(protocols::wireguard::PreSharedKey);
|
||||
}
|
||||
peerObj[configKey::hostName] = peerUrl.host();
|
||||
peerObj[configKey::port] = peerUrl.port() != -1 ? peerUrl.port() : QString(protocols::wireguard::defaultPort).toInt();
|
||||
peerObj[configKey::allowedIps] = QJsonArray::fromStringList(peerMap.value(protocols::wireguard::AllowedIPs).split(", "));
|
||||
if (!peerMap.value(protocols::wireguard::PersistentKeepalive).isEmpty()) {
|
||||
peerObj[configKey::persistentKeepAlive] = peerMap.value(protocols::wireguard::PersistentKeepalive);
|
||||
}
|
||||
peersArray.append(peerObj);
|
||||
}
|
||||
lastConfig["peers"] = peersArray;
|
||||
}
|
||||
|
||||
QString protocolName = configKey::wireguard;
|
||||
QString protocolVersion;
|
||||
ConfigTypes detectedType = ConfigTypes::WireGuard;
|
||||
@@ -588,25 +627,25 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
};
|
||||
|
||||
bool hasAllRequiredFields = std::all_of(requiredJunkFields.begin(), requiredJunkFields.end(),
|
||||
[&configMap](const QString &field) { return !configMap.value(field).isEmpty(); });
|
||||
[&interfaceMap](const QString &field) { return !interfaceMap.value(field).isEmpty(); });
|
||||
if (hasAllRequiredFields) {
|
||||
for (const QString &field : requiredJunkFields) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
lastConfig[field] = interfaceMap.value(field);
|
||||
}
|
||||
|
||||
for (const QString &field : optionalJunkFields) {
|
||||
if (!configMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = configMap.value(field);
|
||||
if (!interfaceMap.value(field).isEmpty()) {
|
||||
lastConfig[field] = interfaceMap.value(field);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasCookieReplyPacketJunkSize = !configMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !configMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !configMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!configMap.value(configKey::specialJunk5).isEmpty();
|
||||
bool hasCookieReplyPacketJunkSize = !interfaceMap.value(configKey::cookieReplyPacketJunkSize).isEmpty();
|
||||
bool hasTransportPacketJunkSize = !interfaceMap.value(configKey::transportPacketJunkSize).isEmpty();
|
||||
bool hasSpecialJunk = !interfaceMap.value(configKey::specialJunk1).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk2).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk3).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk4).isEmpty() ||
|
||||
!interfaceMap.value(configKey::specialJunk5).isEmpty();
|
||||
|
||||
if (hasCookieReplyPacketJunkSize && hasTransportPacketJunkSize) {
|
||||
protocolVersion = "2";
|
||||
@@ -617,8 +656,8 @@ QJsonObject ImportController::extractWireGuardConfig(const QString &data, Config
|
||||
detectedType = ConfigTypes::Awg;
|
||||
}
|
||||
|
||||
if (!configMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = configMap.value(protocols::wireguard::MTU);
|
||||
if (!interfaceMap.value(protocols::wireguard::MTU).isEmpty()) {
|
||||
lastConfig[configKey::mtu] = interfaceMap.value(protocols::wireguard::MTU);
|
||||
} else {
|
||||
lastConfig[configKey::mtu] = (protocolName == configKey::awg)
|
||||
? protocols::awg::defaultMtu
|
||||
|
||||
@@ -72,16 +72,6 @@ namespace
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString buildRemoveContainerScript(const amnezia::ScriptVars &vars, bool removeDataVolume)
|
||||
{
|
||||
QString script = SshSession::replaceVars(amnezia::scriptData(SharedScriptType::remove_container), vars);
|
||||
if (removeDataVolume) {
|
||||
script += QLatin1String("\nsudo docker volume rm -f $CONTAINER_NAME-data 2>/dev/null || true");
|
||||
script = SshSession::replaceVars(script, vars);
|
||||
}
|
||||
return script;
|
||||
}
|
||||
}
|
||||
|
||||
InstallController::InstallController(SecureServersRepository *serversRepository,
|
||||
@@ -130,10 +120,14 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return e;
|
||||
qDebug().noquote() << "InstallController::setupContainer prepareHostWorker finished";
|
||||
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = !isUpdate && (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
if (!isUpdate) {
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
}
|
||||
sshSession.runScript(credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
||||
removeContainerVars));
|
||||
qDebug().noquote() << "InstallController::setupContainer removeContainer finished";
|
||||
|
||||
qDebug().noquote() << "buildContainerWorker start";
|
||||
@@ -158,8 +152,8 @@ ErrorCode InstallController::setupContainer(const ServerCredentials &credentials
|
||||
return startupContainerWorker(credentials, container, config, sshSession);
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
ErrorCode InstallController::updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig,
|
||||
ContainerConfig &newConfig)
|
||||
{
|
||||
if (!isUpdateDockerContainerRequired(container, oldConfig, newConfig)) {
|
||||
auto adminConfig = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
@@ -191,7 +185,7 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
SshSession sshSession(this);
|
||||
|
||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||
qDebug() << "InstallController::updateServerConfig for container" << container << "reinstall required is" << reinstallRequired;
|
||||
qDebug() << "InstallController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||
|
||||
bool xrayServerSettingsChanged = false;
|
||||
if (container == DockerContainer::Xray || container == DockerContainer::SSXray) {
|
||||
@@ -219,11 +213,11 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
if (errorCode == ErrorCode::NoError && xrayServerSettingsChanged && !skipXrayInboundSync) {
|
||||
DnsSettings dnsSettings = { m_appSettingsRepository->primaryDns(), m_appSettingsRepository->secondaryDns() };
|
||||
XrayConfigurator xrayConfigurator(&sshSession);
|
||||
qDebug() << "InstallController::updateServerConfig applying Xray server inbound sync, reinstall="
|
||||
qDebug() << "InstallController::updateContainer applying Xray server inbound sync, reinstall="
|
||||
<< reinstallRequired;
|
||||
errorCode = xrayConfigurator.applyServerSettingsToRemote(credentials, container, newConfig, dnsSettings, false);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
qDebug() << "InstallController::updateServerConfig Xray inbound sync failed, error="
|
||||
qDebug() << "InstallController::updateContainer Xray inbound sync failed, error="
|
||||
<< static_cast<int>(errorCode);
|
||||
}
|
||||
}
|
||||
@@ -234,9 +228,7 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
} else if (container == DockerContainer::Telemt) {
|
||||
TelemtInstaller::uploadClientSettingsSnapshot(sshSession, credentials, container, newConfig);
|
||||
}
|
||||
if (reinstallRequired) {
|
||||
clearCachedProfile(serverId, container);
|
||||
}
|
||||
clearCachedProfile(serverId, container);
|
||||
adminConfig->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, adminConfig->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
}
|
||||
@@ -244,41 +236,6 @@ ErrorCode InstallController::updateServerConfig(const QString &serverId, DockerC
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
ErrorCode InstallController::updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig)
|
||||
{
|
||||
switch (m_serversRepository->serverKind(serverId)) {
|
||||
case serverConfigUtils::ConfigType::SelfHostedAdmin: {
|
||||
auto config = m_serversRepository->selfHostedAdminConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedAdmin);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::SelfHostedUser: {
|
||||
auto config = m_serversRepository->selfHostedUserConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::SelfHostedUser);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
case serverConfigUtils::ConfigType::Native: {
|
||||
auto config = m_serversRepository->nativeConfig(serverId);
|
||||
if (!config.has_value()) {
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
config->updateContainerConfig(container, newConfig);
|
||||
m_serversRepository->editServer(serverId, config->toJson(), serverConfigUtils::ConfigType::Native);
|
||||
return ErrorCode::NoError;
|
||||
}
|
||||
default:
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
}
|
||||
|
||||
void InstallController::clearCachedProfile(const QString &serverId, DockerContainer container)
|
||||
{
|
||||
if (ContainerUtils::containerService(container) == ServiceType::Other) {
|
||||
@@ -838,8 +795,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
||||
qDebug().noquote() << "InstallController::installDockerWorker" << stdOut;
|
||||
|
||||
if (container == DockerContainer::Awg2) {
|
||||
QRegularExpression kernelVersionRegex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = kernelVersionRegex.match(stdOut);
|
||||
QRegularExpression regex(R"(Linux\s+(\d+)\.(\d+)[^\d]*)");
|
||||
QRegularExpressionMatch match = regex.match(stdOut);
|
||||
if (match.hasMatch()) {
|
||||
int majorVersion = match.captured(1).toInt();
|
||||
int minorVersion = match.captured(2).toInt();
|
||||
@@ -852,19 +809,8 @@ ErrorCode InstallController::installDockerWorker(const ServerCredentials &creden
|
||||
|
||||
if (stdOut.contains("lock"))
|
||||
return ErrorCode::ServerPacketManagerError;
|
||||
if (stdOut.contains("Container runtime is not supported"))
|
||||
return ErrorCode::ServerContainerRuntimeNotSupported;
|
||||
|
||||
QRegularExpression notFoundRegex(
|
||||
R"(^.*(?:sudo:|docker:).*not found.*$)",
|
||||
QRegularExpression::MultilineOption);
|
||||
|
||||
if (notFoundRegex.match(stdOut).hasMatch()) {
|
||||
if (stdOut.contains("command not found"))
|
||||
return ErrorCode::ServerDockerFailedError;
|
||||
}
|
||||
|
||||
if (stdOut.contains("Container runtime service not running"))
|
||||
return ErrorCode::ContainerRuntimeServiceNotRunning;
|
||||
|
||||
return error;
|
||||
}
|
||||
@@ -901,7 +847,7 @@ ErrorCode InstallController::isUserInSudo(const ServerCredentials &credentials,
|
||||
return ErrorCode::ServerUserNotInSudo;
|
||||
if (stdOut.contains("can't cd to") || stdOut.contains("Permission denied") || stdOut.contains("No such file or directory"))
|
||||
return ErrorCode::ServerUserDirectoryNotAccessible;
|
||||
if (stdOut.contains(QRegularExpression(R"(\bsudoers\b)")) || stdOut.contains("is not allowed to") || stdOut.contains("can't do that"))
|
||||
if (stdOut.contains("sudoers") || stdOut.contains("is not allowed to run sudo on"))
|
||||
return ErrorCode::ServerUserNotAllowedInSudoers;
|
||||
if (stdOut.contains("password is required") || stdOut.contains("authentication is required"))
|
||||
return ErrorCode::ServerUserPasswordRequired;
|
||||
@@ -1034,11 +980,12 @@ ErrorCode InstallController::removeContainer(const QString &serverId, DockerCont
|
||||
return ErrorCode::InternalError;
|
||||
}
|
||||
SshSession sshSession(this);
|
||||
const amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::ScriptVars removeContainerVars =
|
||||
amnezia::genBaseVars(credentials, container, QString(), QString());
|
||||
const bool removeDataVolume = (container == DockerContainer::MtProxy || container == DockerContainer::Telemt);
|
||||
ErrorCode errorCode =
|
||||
sshSession.runScript(credentials, buildRemoveContainerScript(removeContainerVars, removeDataVolume));
|
||||
removeContainerVars.append({ { "$REMOVE_CONTAINER_DATA", QStringLiteral("1") } });
|
||||
ErrorCode errorCode = sshSession.runScript(
|
||||
credentials,
|
||||
sshSession.replaceVars(amnezia::scriptData(SharedScriptType::remove_container), removeContainerVars));
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
QMap<DockerContainer, ContainerConfig> containers = adminConfig->containers;
|
||||
@@ -1516,7 +1463,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = containerAndPortMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
if (container == DockerContainer::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -1541,7 +1488,7 @@ ErrorCode InstallController::getAlreadyInstalledContainers(const ServerCredentia
|
||||
QString transportProtoStr = torOrDnsRegMatch.captured(3);
|
||||
DockerContainer container = ContainerUtils::containerFromString(name);
|
||||
|
||||
if (container == DockerContainer::None || ContainerUtils::isUnsupportedContainer(container)) {
|
||||
if (container == DockerContainer::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,12 +34,7 @@ public:
|
||||
~InstallController();
|
||||
|
||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, ContainerConfig &config, bool isUpdate = false);
|
||||
|
||||
// Updates server-side container settings (admin self-hosted only): reconfigures the container over SSH.
|
||||
ErrorCode updateServerConfig(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
// Updates client-local settings only: rewrites the stored container config for any self-hosted/native server. No SSH.
|
||||
ErrorCode updateClientConfig(const QString &serverId, DockerContainer container, ContainerConfig &newConfig);
|
||||
ErrorCode updateContainer(const QString &serverId, DockerContainer container, const ContainerConfig &oldConfig, ContainerConfig &newConfig);
|
||||
|
||||
ErrorCode rebootServer(const QString &serverId);
|
||||
ErrorCode removeAllContainers(const QString &serverId);
|
||||
|
||||
@@ -698,7 +698,7 @@ ErrorCode UsersController::revokeXray(const int row,
|
||||
|
||||
QString restartScript = QString("sudo docker restart $CONTAINER_NAME");
|
||||
error = sshSession->runScript(
|
||||
credentials,
|
||||
credentials,
|
||||
sshSession->replaceVars(restartScript, amnezia::genBaseVars(credentials, container, QString(), QString()))
|
||||
);
|
||||
if (error != ErrorCode::NoError) {
|
||||
@@ -758,17 +758,14 @@ ErrorCode UsersController::revokeClient(const QString &serverId, const int index
|
||||
ContainerConfig containerCfg = adminConfig->containerConfig(container);
|
||||
QString containerClientId = containerCfg.protocolConfig.clientId();
|
||||
|
||||
const bool isAdminMatch = !clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId);
|
||||
if (isAdminMatch) {
|
||||
if (!clientId.isEmpty() && !containerClientId.isEmpty() && containerClientId.contains(clientId)) {
|
||||
emit adminConfigRevoked(serverId, container);
|
||||
}
|
||||
|
||||
emit clientRevoked(index);
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
}
|
||||
|
||||
emit clientsUpdated(m_clientsTable);
|
||||
emit revokeFinished(errorCode);
|
||||
|
||||
return errorCode;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ signals:
|
||||
void clientAdded(const QJsonObject &client);
|
||||
void clientRenamed(int row, const QString &newName);
|
||||
void clientRevoked(int row);
|
||||
void revokeFinished(ErrorCode errorCode);
|
||||
void adminConfigRevoked(const QString &serverId, DockerContainer container);
|
||||
|
||||
public slots:
|
||||
|
||||
@@ -205,7 +205,11 @@ QJsonObject AwgClientConfig::toJson() const
|
||||
if (isObfuscationEnabled) {
|
||||
obj[configKey::isObfuscationEnabled] = isObfuscationEnabled;
|
||||
}
|
||||
|
||||
|
||||
if (!peers.isEmpty()) {
|
||||
obj["peers"] = peers;
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -250,7 +254,9 @@ AwgClientConfig AwgClientConfig::fromJson(const QJsonObject& json)
|
||||
config.specialJunk5 = json.value(configKey::specialJunk5).toString();
|
||||
|
||||
config.isObfuscationEnabled = json.value(configKey::isObfuscationEnabled).toBool(false);
|
||||
|
||||
|
||||
config.peers = json.value("peers").toArray();
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifndef AWGPROTOCOLCONFIG_H
|
||||
#define AWGPROTOCOLCONFIG_H
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
@@ -60,6 +61,7 @@ struct AwgClientConfig {
|
||||
QStringList allowedIps;
|
||||
QString persistentKeepAlive;
|
||||
QString mtu;
|
||||
QJsonArray peers;
|
||||
QString junkPacketCount;
|
||||
QString junkPacketMinSize;
|
||||
QString junkPacketMaxSize;
|
||||
|
||||
@@ -32,7 +32,7 @@ XrayXPaddingConfig XrayXPaddingConfig::fromJson(const QJsonObject &json)
|
||||
c.bytesMin = json.value(configKey::xPaddingBytesMin).toString();
|
||||
c.bytesMax = json.value(configKey::xPaddingBytesMax).toString();
|
||||
c.obfsMode = json.value(configKey::xPaddingObfsMode).toBool(true);
|
||||
c.key = json.value(configKey::xPaddingKey).toString();
|
||||
c.key = json.value(configKey::xPaddingKey).toString(protocols::xray::defaultSite);
|
||||
c.header = json.value(configKey::xPaddingHeader).toString();
|
||||
c.placement = json.value(configKey::xPaddingPlacement).toString(protocols::xray::defaultXPaddingPlacement);
|
||||
c.method = json.value(configKey::xPaddingMethod).toString(protocols::xray::defaultXPaddingMethod);
|
||||
@@ -365,8 +365,6 @@ XrayServerConfig XrayServerConfig::fromJson(const QJsonObject &json)
|
||||
bool XrayServerConfig::hasEqualServerSettings(const XrayServerConfig &other) const
|
||||
{
|
||||
return port == other.port
|
||||
&& transportProto == other.transportProto
|
||||
&& subnetAddress == other.subnetAddress
|
||||
&& site == other.site
|
||||
&& security == other.security
|
||||
&& flow == other.flow
|
||||
@@ -468,17 +466,6 @@ XrayProtocolConfig XrayProtocolConfig::fromJson(const QJsonObject &json)
|
||||
}
|
||||
}
|
||||
}
|
||||
const QJsonArray outbounds = parsed.value(protocols::xray::outbounds).toArray();
|
||||
if (!outbounds.isEmpty()) {
|
||||
const QJsonObject settings = outbounds[0].toObject().value(protocols::xray::settings).toObject();
|
||||
const QJsonArray vnext = settings.value(protocols::xray::vnext).toArray();
|
||||
if (!vnext.isEmpty()) {
|
||||
const QJsonArray users = vnext[0].toObject().value(protocols::xray::users).toArray();
|
||||
if (!users.isEmpty()) {
|
||||
clientCfg.id = users[0].toObject().value(protocols::xray::id).toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
c.clientConfig = clientCfg;
|
||||
} else {
|
||||
c.clientConfig = XrayClientConfig::fromJson(parsed);
|
||||
|
||||
@@ -29,11 +29,6 @@ ContainerConfig NativeServerConfig::containerConfig(DockerContainer container) c
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void NativeServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> NativeServerConfig::getDnsPair(const QString &primaryDns, const QString &secondaryDns) const
|
||||
{
|
||||
QString d1 = dns1;
|
||||
|
||||
@@ -27,8 +27,6 @@ struct NativeServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -43,11 +43,6 @@ ContainerConfig SelfHostedUserServerConfig::containerConfig(DockerContainer cont
|
||||
return containers.value(container);
|
||||
}
|
||||
|
||||
void SelfHostedUserServerConfig::updateContainerConfig(DockerContainer container, const ContainerConfig &config)
|
||||
{
|
||||
containers[container] = config;
|
||||
}
|
||||
|
||||
QPair<QString, QString> SelfHostedUserServerConfig::getDnsPair(const QString &primaryDns,
|
||||
const QString &secondaryDns) const
|
||||
{
|
||||
|
||||
@@ -32,8 +32,6 @@ struct SelfHostedUserServerConfig {
|
||||
bool hasContainers() const;
|
||||
ContainerConfig containerConfig(DockerContainer container) const;
|
||||
|
||||
void updateContainerConfig(DockerContainer container, const ContainerConfig &config);
|
||||
|
||||
QPair<QString, QString> getDnsPair(const QString &primaryDns, const QString &secondaryDns) const;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
|
||||
@@ -39,44 +39,33 @@ QString OpenVpnProtocol::defaultConfigPath()
|
||||
return p;
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::cleanupResources()
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
if (m_openVpnProcess || openVpnProcessIsRunning()) {
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
|
||||
// TODO: need refactoring
|
||||
// sendTermSignal() will even return true while server connected ???
|
||||
if ((m_connectionState == Vpn::ConnectionState::Preparing) || (m_connectionState == Vpn::ConnectionState::Connecting)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Connected)
|
||||
|| (m_connectionState == Vpn::ConnectionState::Reconnecting)) {
|
||||
if (!sendTermSignal()) {
|
||||
killOpenVpnProcess();
|
||||
}
|
||||
QThread::msleep(10);
|
||||
m_managementServer.stop();
|
||||
}
|
||||
m_managementServer.stop();
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
|
||||
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
|
||||
QRemoteObjectPendingReply<bool> reply = iface->disableKillSwitch();
|
||||
if (!reply.waitForFinished(1000) && !reply.returnValue()) {
|
||||
qWarning() << "OpenVpnProtocol::cleanupResources(): Failed to disable killswitch";
|
||||
qWarning() << "OpenVpnProtocol::stop(): Failed to disable killswitch";
|
||||
}
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
void OpenVpnProtocol::stop()
|
||||
{
|
||||
qDebug() << "OpenVpnProtocol::stop()";
|
||||
|
||||
const bool wasActive = m_connectionState == Vpn::ConnectionState::Preparing
|
||||
|| m_connectionState == Vpn::ConnectionState::Connecting
|
||||
|| m_connectionState == Vpn::ConnectionState::Connected
|
||||
|| m_connectionState == Vpn::ConnectionState::Reconnecting;
|
||||
|
||||
if (wasActive) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnecting);
|
||||
}
|
||||
|
||||
cleanupResources();
|
||||
|
||||
if (wasActive || m_connectionState == Vpn::ConnectionState::Disconnecting) {
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
setConnectionState(Vpn::ConnectionState::Disconnected);
|
||||
}
|
||||
|
||||
ErrorCode OpenVpnProtocol::prepare()
|
||||
@@ -179,7 +168,7 @@ void OpenVpnProtocol::updateRouteGateway(QString line)
|
||||
|
||||
ErrorCode OpenVpnProtocol::start()
|
||||
{
|
||||
cleanupResources();
|
||||
OpenVpnProtocol::stop();
|
||||
|
||||
if (!QFileInfo::exists(configPath())) {
|
||||
setLastError(ErrorCode::OpenVpnConfigMissing);
|
||||
|
||||
@@ -29,7 +29,6 @@ protected slots:
|
||||
void onReadyReadDataFromManagementServer();
|
||||
|
||||
private:
|
||||
void cleanupResources();
|
||||
QString configPath() const;
|
||||
bool openVpnProcessIsRunning() const;
|
||||
bool sendTermSignal();
|
||||
|
||||
@@ -208,8 +208,8 @@ QString SecureServersRepository::nextAvailableServerName() const
|
||||
int i = 0;
|
||||
QString candidate;
|
||||
do {
|
||||
++i;
|
||||
candidate = tr("Server") + QLatin1Char(' ') + QString::number(i);
|
||||
i++;
|
||||
candidate = QStringLiteral("Server %1").arg(i);
|
||||
} while (usedNames.contains(candidate));
|
||||
|
||||
return candidate;
|
||||
|
||||
@@ -15,8 +15,6 @@ namespace amnezia
|
||||
Awg2,
|
||||
WireGuard,
|
||||
OpenVpn,
|
||||
Cloak,
|
||||
ShadowSocks,
|
||||
Ipsec,
|
||||
Xray,
|
||||
SSXray,
|
||||
|
||||
@@ -21,8 +21,6 @@ QString ContainerUtils::containerToString(DockerContainer c)
|
||||
{
|
||||
if (c == DockerContainer::None)
|
||||
return "none";
|
||||
if (c == DockerContainer::Cloak)
|
||||
return "amnezia-openvpn-cloak";
|
||||
if (c == DockerContainer::Awg)
|
||||
return "amnezia-awg";
|
||||
if (c == DockerContainer::Awg2)
|
||||
@@ -64,8 +62,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerHumanNames()
|
||||
{
|
||||
return { { DockerContainer::None, "Not installed" },
|
||||
{ DockerContainer::OpenVpn, "OpenVPN" },
|
||||
{ DockerContainer::ShadowSocks, "OpenVPN over SS" },
|
||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||
{ DockerContainer::WireGuard, "WireGuard" },
|
||||
{ DockerContainer::Awg, "AmneziaWG" },
|
||||
{ DockerContainer::Awg2, "AmneziaWG" },
|
||||
@@ -87,10 +83,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerDescriptions()
|
||||
return { { DockerContainer::OpenVpn,
|
||||
QObject::tr("OpenVPN is the most popular VPN protocol, with flexible configuration options. It uses its "
|
||||
"own security protocol with SSL/TLS for key exchange.") },
|
||||
{ DockerContainer::ShadowSocks,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::Cloak,
|
||||
QObject::tr("This protocol is no longer supported.") },
|
||||
{ DockerContainer::WireGuard,
|
||||
QObject::tr("WireGuard - popular VPN protocol with high performance, high speed and low power "
|
||||
"consumption.") },
|
||||
@@ -202,9 +194,6 @@ QMap<DockerContainer, QString> ContainerUtils::containerDetailedDescriptions()
|
||||
|
||||
ServiceType ContainerUtils::containerService(DockerContainer c)
|
||||
{
|
||||
if (isUnsupportedContainer(c)) {
|
||||
return ServiceType::Vpn;
|
||||
}
|
||||
return ProtocolUtils::protocolService(defaultProtocol(c));
|
||||
}
|
||||
|
||||
@@ -213,8 +202,6 @@ Proto ContainerUtils::defaultProtocol(DockerContainer c)
|
||||
switch (c) {
|
||||
case DockerContainer::None: return Proto::Unknown;
|
||||
case DockerContainer::OpenVpn: return Proto::OpenVpn;
|
||||
case DockerContainer::Cloak:
|
||||
case DockerContainer::ShadowSocks: return Proto::Unknown;
|
||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||
case DockerContainer::Awg2: return Proto::Awg;
|
||||
case DockerContainer::Awg: return Proto::Awg;
|
||||
@@ -265,8 +252,6 @@ bool ContainerUtils::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
// macOS build using Network Extension – allow OpenVPN for parity with iOS.
|
||||
switch (c) {
|
||||
case DockerContainer::OpenVpn: return true;
|
||||
case DockerContainer::Cloak: return false;
|
||||
case DockerContainer::ShadowSocks: return false;
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg2: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
@@ -351,10 +336,6 @@ int ContainerUtils::easySetupOrder(DockerContainer container)
|
||||
|
||||
bool ContainerUtils::isShareable(DockerContainer container)
|
||||
{
|
||||
if (isUnsupportedContainer(container)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (container) {
|
||||
case DockerContainer::TorWebSite: return false;
|
||||
case DockerContainer::Dns: return false;
|
||||
@@ -371,11 +352,6 @@ bool ContainerUtils::isAwgContainer(DockerContainer container)
|
||||
return container == DockerContainer::Awg || container == DockerContainer::Awg2;
|
||||
}
|
||||
|
||||
bool ContainerUtils::isUnsupportedContainer(DockerContainer container)
|
||||
{
|
||||
return container == DockerContainer::Cloak || container == DockerContainer::ShadowSocks;
|
||||
}
|
||||
|
||||
QJsonObject ContainerUtils::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||
{
|
||||
QString protocolConfigString = containerConfig.value(ProtocolUtils::protoToString(protocol))
|
||||
|
||||
@@ -45,8 +45,6 @@ namespace amnezia
|
||||
|
||||
bool isAwgContainer(DockerContainer container);
|
||||
|
||||
bool isUnsupportedContainer(DockerContainer container);
|
||||
|
||||
QJsonObject getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig);
|
||||
|
||||
int installPageOrder(DockerContainer container);
|
||||
|
||||
@@ -38,8 +38,6 @@ namespace amnezia
|
||||
XrayServerConfigInvalid = 215,
|
||||
XrayServerNoVlessClients = 216,
|
||||
XrayRealityKeysReadFailed = 217,
|
||||
ServerContainerRuntimeNotSupported = 218,
|
||||
ContainerRuntimeServiceNotRunning = 219,
|
||||
|
||||
// Ssh connection errors
|
||||
SshRequestDeniedError = 300,
|
||||
@@ -81,7 +79,6 @@ namespace amnezia
|
||||
ImportBackupFileUseRestoreInstead = 903,
|
||||
RestoreBackupInvalidError = 904,
|
||||
LegacyApiV1NotSupportedError = 905,
|
||||
LegacyContainerNotSupportedError = 906,
|
||||
|
||||
// Android errors
|
||||
AndroidError = 1000,
|
||||
@@ -126,3 +123,5 @@ namespace amnezia
|
||||
Q_DECLARE_METATYPE(amnezia::ErrorCode)
|
||||
|
||||
#endif // ERRORCODES_H
|
||||
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ QString errorString(ErrorCode code) {
|
||||
case(ErrorCode::XrayRealityKeysReadFailed):
|
||||
errorMessage = QObject::tr("Server error: failed to read XRay Reality keys from the server");
|
||||
break;
|
||||
case(ErrorCode::ServerContainerRuntimeNotSupported): errorMessage = QObject::tr("Server error: The default container runtime available for installation on this server is not supported.\n Install Docker Engine on the server manually and try again."); break;
|
||||
case(ErrorCode::ContainerRuntimeServiceNotRunning): errorMessage = QObject::tr("Container runtime error: The container runtime service is not running.\n Check the container runtime service on the server, or wait about a minute and try again."); break;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("SSH request was denied"); break;
|
||||
@@ -71,7 +69,6 @@ QString errorString(ErrorCode code) {
|
||||
case (ErrorCode::ImportBackupFileUseRestoreInstead): errorMessage = QObject::tr("Backup files cannot be imported here. Use 'Restore from backup' instead."); break;
|
||||
case (ErrorCode::RestoreBackupInvalidError): errorMessage = QObject::tr("Backup file is corrupted or has invalid format"); break;
|
||||
case (ErrorCode::LegacyApiV1NotSupportedError): errorMessage = QObject::tr("This legacy Amnezia subscription format is no longer supported"); break;
|
||||
case (ErrorCode::LegacyContainerNotSupportedError): errorMessage = QObject::tr("This protocol is no longer supported. Please select another protocol or remove this container from the server settings."); break;
|
||||
case (ErrorCode::ImportOpenConfigError): errorMessage = QObject::tr("Unable to open config file"); break;
|
||||
case (ErrorCode::NoInstalledContainersError): errorMessage = QObject::tr("VPN Protocols is not installed.\n Please install VPN container at first"); break;
|
||||
|
||||
|
||||
@@ -286,7 +286,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
return { resGateway, QNetworkInterface::interfaceFromIndex(resIndex) };
|
||||
#endif
|
||||
#ifdef Q_OS_LINUX
|
||||
constexpr int BUFFER_SIZE = 8192;
|
||||
constexpr int BUFFER_SIZE = 100;
|
||||
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||
int sock = -1, msgseq = 0;
|
||||
struct nlmsghdr *nlh, *nlmsg;
|
||||
@@ -294,7 +294,7 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
// This struct contain route attributes (route type)
|
||||
struct rtattr *route_attribute;
|
||||
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||
char msgbuf[100], buffer[BUFFER_SIZE];
|
||||
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||
char *ptr = buffer;
|
||||
struct timeval tv;
|
||||
|
||||
@@ -339,8 +339,8 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
nlh = (struct nlmsghdr *) ptr;
|
||||
|
||||
/* Check if the header is valid */
|
||||
if((NLMSG_OK(nlh, received_bytes) == 0) ||
|
||||
(nlh->nlmsg_type == NLMSG_ERROR))
|
||||
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||
{
|
||||
perror("Error in received packet");
|
||||
return {};
|
||||
@@ -355,15 +355,13 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
}
|
||||
|
||||
/* Break if its not a multi part message */
|
||||
if ((nlh->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||
break;
|
||||
}
|
||||
while ((nlh->nlmsg_seq != msgseq) || (nlh->nlmsg_pid != getpid()));
|
||||
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||
|
||||
/* parse response */
|
||||
int remaining = msg_len + received_bytes;
|
||||
nlh = (struct nlmsghdr *) buffer;
|
||||
for ( ; NLMSG_OK(nlh, remaining); nlh = NLMSG_NEXT(nlh, remaining))
|
||||
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||
{
|
||||
/* Get the route data */
|
||||
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||
@@ -372,10 +370,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||
continue;
|
||||
|
||||
/* Reset per-route to avoid cross-route state pollution */
|
||||
memset(gateway_address, 0, sizeof(gateway_address));
|
||||
memset(interface, 0, sizeof(interface));
|
||||
|
||||
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||
|
||||
@@ -401,8 +395,6 @@ QPair<QString, QNetworkInterface> NetworkUtilities::getGatewayAndIface()
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!(*gateway_address) || !(*interface))
|
||||
qDebug() << "getGatewayAndIface: no gateway found";
|
||||
close(sock);
|
||||
return { gateway_address, QNetworkInterface::interfaceFromName(interface) };
|
||||
#endif
|
||||
|
||||
@@ -441,6 +441,37 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
config.m_specialJunk["I5"] = obj.value("I5").toString();
|
||||
}
|
||||
|
||||
if (obj.contains("primaryPeerAllowedIPAddressRanges") &&
|
||||
obj.value("primaryPeerAllowedIPAddressRanges").isArray()) {
|
||||
for (const QJsonValue& ipVal : obj.value("primaryPeerAllowedIPAddressRanges").toArray()) {
|
||||
if (!ipVal.isObject()) continue;
|
||||
QJsonObject ipObj = ipVal.toObject();
|
||||
config.m_primaryPeerAllowedIPRanges.append(
|
||||
IPAddress(QHostAddress(ipObj.value("address").toString()),
|
||||
ipObj.value("range").toInt()));
|
||||
}
|
||||
}
|
||||
|
||||
if (obj.contains("additionalPeers") && obj.value("additionalPeers").isArray()) {
|
||||
for (const QJsonValue& peerVal : obj.value("additionalPeers").toArray()) {
|
||||
if (!peerVal.isObject()) continue;
|
||||
QJsonObject peerObj = peerVal.toObject();
|
||||
InterfaceConfig::AdditionalPeerConfig peer;
|
||||
peer.m_serverPublicKey = peerObj.value("serverPublicKey").toString();
|
||||
peer.m_serverPskKey = peerObj.value("serverPskKey").toString();
|
||||
peer.m_serverIpv4AddrIn = peerObj.value("serverIpv4AddrIn").toString();
|
||||
peer.m_serverPort = peerObj.value("serverPort").toInt();
|
||||
for (const QJsonValue& ipVal : peerObj.value("allowedIPAddressRanges").toArray()) {
|
||||
if (!ipVal.isObject()) continue;
|
||||
QJsonObject ipObj = ipVal.toObject();
|
||||
peer.m_allowedIPAddressRanges.append(
|
||||
IPAddress(QHostAddress(ipObj.value("address").toString()),
|
||||
ipObj.value("range").toInt()));
|
||||
}
|
||||
config.m_additionalPeers.append(peer);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -613,7 +644,7 @@ void Daemon::checkHandshake() {
|
||||
pendingHandshakes++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Check again if there were connections that haven't completed a handshake.
|
||||
if (pendingHandshakes > 0) {
|
||||
m_handshakeTimer.start(HANDSHAKE_POLL_MSEC);
|
||||
|
||||
@@ -37,6 +37,9 @@ class InterfaceConfig {
|
||||
int m_serverPort = 0;
|
||||
int m_deviceMTU = 1420;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
// For multi-peer: primary peer's own IPs only (used for UAPI allowed_ips).
|
||||
// Empty for single-peer (falls back to m_allowedIPAddressRanges).
|
||||
QList<IPAddress> m_primaryPeerAllowedIPRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
QStringList m_allowedDnsServers;
|
||||
@@ -58,6 +61,15 @@ class InterfaceConfig {
|
||||
QString m_transportPacketMagicHeader;
|
||||
QMap<QString, QString> m_specialJunk;
|
||||
|
||||
struct AdditionalPeerConfig {
|
||||
QString m_serverPublicKey;
|
||||
QString m_serverPskKey;
|
||||
QString m_serverIpv4AddrIn;
|
||||
int m_serverPort = 0;
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
};
|
||||
QList<AdditionalPeerConfig> m_additionalPeers;
|
||||
|
||||
QJsonObject toJson() const;
|
||||
QString toWgConf(
|
||||
const QMap<QString, QString>& extra = QMap<QString, QString>()) const;
|
||||
|
||||
@@ -25,8 +25,6 @@ set_target_properties(AmneziaVPNNetworkExtension PROPERTIES
|
||||
|
||||
XCODE_ATTRIBUTE_INFOPLIST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/Info.plist.in
|
||||
XCODE_ATTRIBUTE_LD_RUNPATH_SEARCH_PATHS "@executable_path/../../../../Frameworks @loader_path/../../../../Frameworks"
|
||||
|
||||
XCODE_LINK_BUILD_PHASE_MODE KNOWN_LOCATION
|
||||
)
|
||||
|
||||
if(DEPLOY)
|
||||
@@ -120,20 +118,10 @@ target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CLIENT_ROOT_DIR}
|
||||
target_include_directories(AmneziaVPNNetworkExtension PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
find_package(openvpnadapter REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET amnezia::openvpnadapter APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::openvpnadapter)
|
||||
|
||||
find_package(awg-apple REQUIRED)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE amnezia::awg-apple)
|
||||
|
||||
find_package(hev-socks5-tunnel REQUIRED)
|
||||
# FIXME(ygurov): https://github.com/conan-io/conan/issues/20034
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS DEBUG)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS MINSIZEREL)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE)
|
||||
set_property(TARGET heiher::hev-socks5-tunnel APPEND PROPERTY IMPORTED_CONFIGURATIONS RELWITHDEBINFO)
|
||||
target_link_libraries(AmneziaVPNNetworkExtension PRIVATE heiher::hev-socks5-tunnel)
|
||||
|
||||
@@ -169,68 +169,96 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
|
||||
QJsonArray jsAllowedIPAddesses;
|
||||
|
||||
QJsonArray plainAllowedIP = wgConfig.value(amnezia::configKey::allowedIps).toArray();
|
||||
QJsonArray defaultAllowedIP = { "0.0.0.0/0", "::/0" };
|
||||
auto ipRangeToJson = [](const QString& ipRange) -> QJsonObject {
|
||||
QJsonObject range;
|
||||
const QStringList parts = ipRange.split('/');
|
||||
range.insert("address", parts[0]);
|
||||
range.insert("range", parts.size() > 1 ? parts[1].toInt() : 32);
|
||||
range.insert("isIpv6", ipRange.contains(':'));
|
||||
return range;
|
||||
};
|
||||
|
||||
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
|
||||
// Use AllowedIP list from WG config because of higher priority
|
||||
for (auto v : plainAllowedIP) {
|
||||
QString ipRange = v.toString();
|
||||
if (ipRange.split('/').size() > 1){
|
||||
QJsonObject range;
|
||||
range.insert("address", ipRange.split('/')[0]);
|
||||
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
} else {
|
||||
QJsonObject range;
|
||||
range.insert("address",ipRange);
|
||||
range.insert("range", 32);
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
QJsonArray peersArray = wgConfig.value("peers").toArray();
|
||||
bool isMultiPeer = peersArray.size() > 1;
|
||||
|
||||
if (isMultiPeer) {
|
||||
// Union of all peers' IPs goes into allowedIPAddressRanges (used for route setup).
|
||||
QSet<QString> seenIps;
|
||||
for (const QJsonValue& peerVal : std::as_const(peersArray)) {
|
||||
for (const QJsonValue& ipVal : peerVal.toObject().value(amnezia::configKey::allowedIps).toArray()) {
|
||||
const QString ipRange = ipVal.toString().trimmed();
|
||||
if (seenIps.contains(ipRange)) continue;
|
||||
seenIps.insert(ipRange);
|
||||
jsAllowedIPAddesses.append(ipRangeToJson(ipRange));
|
||||
}
|
||||
}
|
||||
|
||||
// Primary peer's own IPs only — used for UAPI allowed_ips to avoid trie conflicts.
|
||||
QJsonArray primaryPeerIpsJson;
|
||||
for (const QJsonValue& ipVal : peersArray[0].toObject().value(amnezia::configKey::allowedIps).toArray()) {
|
||||
primaryPeerIpsJson.append(ipRangeToJson(ipVal.toString().trimmed()));
|
||||
}
|
||||
json.insert("primaryPeerAllowedIPAddressRanges", primaryPeerIpsJson);
|
||||
|
||||
QJsonArray additionalPeersJson;
|
||||
for (int i = 1; i < peersArray.size(); ++i) {
|
||||
const QJsonObject peerObj = peersArray[i].toObject();
|
||||
QJsonObject additionalPeer;
|
||||
additionalPeer.insert("serverPublicKey", peerObj.value(amnezia::configKey::serverPubKey));
|
||||
additionalPeer.insert("serverPskKey", peerObj.value(amnezia::configKey::pskKey));
|
||||
additionalPeer.insert("serverIpv4AddrIn", peerObj.value(amnezia::configKey::hostName));
|
||||
additionalPeer.insert("serverPort", peerObj.value(amnezia::configKey::port).toInt());
|
||||
QJsonArray additionalPeerIps;
|
||||
for (const QJsonValue& ipVal : peerObj.value(amnezia::configKey::allowedIps).toArray()) {
|
||||
additionalPeerIps.append(ipRangeToJson(ipVal.toString().trimmed()));
|
||||
}
|
||||
additionalPeer.insert("allowedIPAddressRanges", additionalPeerIps);
|
||||
additionalPeersJson.append(additionalPeer);
|
||||
}
|
||||
json.insert("additionalPeers", additionalPeersJson);
|
||||
} else {
|
||||
QJsonArray plainAllowedIP = wgConfig.value(amnezia::configKey::allowedIps).toArray();
|
||||
QJsonArray defaultAllowedIP = { "0.0.0.0/0", "::/0" };
|
||||
|
||||
// Use APP split tunnel
|
||||
if (plainAllowedIP != defaultAllowedIP && !plainAllowedIP.isEmpty()) {
|
||||
// Use AllowedIP list from WG config because of higher priority
|
||||
for (auto v : plainAllowedIP) {
|
||||
jsAllowedIPAddesses.append(ipRangeToJson(v.toString().trimmed()));
|
||||
}
|
||||
} else {
|
||||
// Use APP split tunnel
|
||||
if (splitTunnelType == 0 || splitTunnelType == 2) {
|
||||
QJsonObject range_ipv4;
|
||||
range_ipv4.insert("address", "0.0.0.0");
|
||||
range_ipv4.insert("range", 0);
|
||||
range_ipv4.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range_ipv4);
|
||||
QJsonObject range_ipv4;
|
||||
range_ipv4.insert("address", "0.0.0.0");
|
||||
range_ipv4.insert("range", 0);
|
||||
range_ipv4.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range_ipv4);
|
||||
|
||||
QJsonObject range_ipv6;
|
||||
range_ipv6.insert("address", "::");
|
||||
range_ipv6.insert("range", 0);
|
||||
range_ipv6.insert("isIpv6", true);
|
||||
jsAllowedIPAddesses.append(range_ipv6);
|
||||
QJsonObject range_ipv6;
|
||||
range_ipv6.insert("address", "::");
|
||||
range_ipv6.insert("range", 0);
|
||||
range_ipv6.insert("isIpv6", true);
|
||||
jsAllowedIPAddesses.append(range_ipv6);
|
||||
}
|
||||
|
||||
if (splitTunnelType == 1) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
if (ipRange.split('/').size() > 1){
|
||||
QJsonObject range;
|
||||
range.insert("address", ipRange.split('/')[0]);
|
||||
range.insert("range", atoi(ipRange.split('/')[1].toLocal8Bit()));
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
} else {
|
||||
QJsonObject range;
|
||||
range.insert("address",ipRange);
|
||||
range.insert("range", 32);
|
||||
range.insert("isIpv6", false);
|
||||
jsAllowedIPAddesses.append(range);
|
||||
}
|
||||
}
|
||||
for (auto v : splitTunnelSites) {
|
||||
jsAllowedIPAddesses.append(ipRangeToJson(v.toString().trimmed()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json.insert("allowedIPAddressRanges", jsAllowedIPAddesses);
|
||||
|
||||
QJsonArray jsExcludedAddresses;
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
|
||||
if (isMultiPeer) {
|
||||
for (const QJsonValue& peerVal : std::as_const(peersArray)) {
|
||||
jsExcludedAddresses.append(peerVal.toObject().value(amnezia::configKey::hostName));
|
||||
}
|
||||
} else {
|
||||
jsExcludedAddresses.append(wgConfig.value(amnezia::configKey::hostName));
|
||||
}
|
||||
if (splitTunnelType == 2) {
|
||||
for (auto v : splitTunnelSites) {
|
||||
QString ipRange = v.toString();
|
||||
|
||||
@@ -20,7 +20,7 @@ extension PacketTunnelProvider {
|
||||
|
||||
let tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgConfigStr)
|
||||
|
||||
if tunnelConfiguration.peers.first!.allowedIPs
|
||||
if tunnelConfiguration.peers.first?.allowedIPs
|
||||
.map({ $0.stringRepresentation })
|
||||
.joined(separator: ", ") == "0.0.0.0/0, ::/0" {
|
||||
if wgConfig.splitTunnelType == 1 {
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
import Foundation
|
||||
|
||||
struct WGPeerConfig: Decodable {
|
||||
let serverPublicKey: String
|
||||
let presharedKey: String?
|
||||
let allowedIPs: [String]
|
||||
let hostName: String
|
||||
let port: Int
|
||||
let persistentKeepAlive: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case serverPublicKey = "server_pub_key"
|
||||
case presharedKey = "psk_key"
|
||||
case allowedIPs = "allowed_ips"
|
||||
case hostName
|
||||
case port
|
||||
case persistentKeepAlive = "persistent_keep_alive"
|
||||
}
|
||||
}
|
||||
|
||||
struct WGConfig: Decodable {
|
||||
let initPacketMagicHeader, responsePacketMagicHeader: String?
|
||||
let underloadPacketMagicHeader, transportPacketMagicHeader: String?
|
||||
@@ -19,6 +37,7 @@ struct WGConfig: Decodable {
|
||||
var persistentKeepAlive: String
|
||||
let splitTunnelType: Int
|
||||
let splitTunnelSites: [String]
|
||||
let peers: [WGPeerConfig]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case initPacketMagicHeader = "H1", responsePacketMagicHeader = "H2"
|
||||
@@ -39,6 +58,7 @@ struct WGConfig: Decodable {
|
||||
case persistentKeepAlive = "persistent_keep_alive"
|
||||
case splitTunnelType
|
||||
case splitTunnelSites
|
||||
case peers
|
||||
}
|
||||
|
||||
var settings: String {
|
||||
@@ -103,7 +123,7 @@ struct WGConfig: Decodable {
|
||||
return settingsLines.joined(separator: "\n")
|
||||
}
|
||||
|
||||
var str: String {
|
||||
private var interfaceSection: String {
|
||||
"""
|
||||
[Interface]
|
||||
Address = \(clientIP)
|
||||
@@ -111,9 +131,30 @@ struct WGConfig: Decodable {
|
||||
MTU = \(mtu)
|
||||
PrivateKey = \(clientPrivateKey)
|
||||
\(settings)
|
||||
"""
|
||||
}
|
||||
|
||||
var str: String {
|
||||
if let peers = peers, !peers.isEmpty {
|
||||
let peerSections = peers.map { peer -> String in
|
||||
var lines = ["[Peer]", "PublicKey = \(peer.serverPublicKey)"]
|
||||
if let psk = peer.presharedKey, !psk.isEmpty {
|
||||
lines.append("PresharedKey = \(psk)")
|
||||
}
|
||||
lines.append("AllowedIPs = \(peer.allowedIPs.joined(separator: ", "))")
|
||||
lines.append("Endpoint = \(peer.hostName):\(peer.port)")
|
||||
if let ka = peer.persistentKeepAlive {
|
||||
lines.append("PersistentKeepalive = \(ka)")
|
||||
}
|
||||
return lines.joined(separator: "\n")
|
||||
}.joined(separator: "\n")
|
||||
return interfaceSection + "\n" + peerSections
|
||||
}
|
||||
return """
|
||||
\(interfaceSection)
|
||||
[Peer]
|
||||
PublicKey = \(serverPublicKey)
|
||||
\(presharedKey == nil ? "" : "PresharedKey = \(presharedKey!)")
|
||||
\((presharedKey?.isEmpty ?? true) ? "" : "PresharedKey = \(presharedKey!)")
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
@@ -121,19 +162,21 @@ struct WGConfig: Decodable {
|
||||
}
|
||||
|
||||
var redux: String {
|
||||
"""
|
||||
let peerCount = peers?.count ?? 1
|
||||
let peerInfo = peers.map { peers in
|
||||
peers.enumerated().map { i, peer in
|
||||
"[Peer \(i + 1)] Endpoint = \(peer.hostName):\(peer.port), AllowedIPs = \(peer.allowedIPs.joined(separator: ", "))"
|
||||
}.joined(separator: "\n")
|
||||
} ?? "Endpoint = \(hostName):\(port), AllowedIPs = \(allowedIPs.joined(separator: ", "))"
|
||||
return """
|
||||
[Interface]
|
||||
Address = \(clientIP)
|
||||
DNS = \(dns1), \(dns2)
|
||||
MTU = \(mtu)
|
||||
PrivateKey = ***
|
||||
\(settings)
|
||||
[Peer]
|
||||
PublicKey = ***
|
||||
PresharedKey = ***
|
||||
AllowedIPs = \(allowedIPs.joined(separator: ", "))
|
||||
Endpoint = \(hostName):\(port)
|
||||
PersistentKeepalive = \(persistentKeepAlive)
|
||||
PeerCount = \(peerCount)
|
||||
\(peerInfo)
|
||||
|
||||
SplitTunnelType = \(splitTunnelType)
|
||||
SplitTunnelSites = \(splitTunnelSites.joined(separator: ", "))
|
||||
|
||||
@@ -595,6 +595,10 @@ bool IosController::setupWireGuard()
|
||||
wgConfig.insert(configKey::persistentKeepAlive, "25");
|
||||
}
|
||||
|
||||
if (config.contains("peers") && config["peers"].isArray()) {
|
||||
wgConfig.insert("peers", config["peers"]);
|
||||
}
|
||||
|
||||
if (config.contains(configKey::isObfuscationEnabled) && config.value(configKey::isObfuscationEnabled).toBool()) {
|
||||
wgConfig.insert(configKey::initPacketMagicHeader, config[configKey::initPacketMagicHeader]);
|
||||
wgConfig.insert(configKey::responsePacketMagicHeader, config[configKey::responsePacketMagicHeader]);
|
||||
@@ -674,7 +678,23 @@ bool IosController::setupAwg()
|
||||
|
||||
wgConfig.insert(configKey::hostName, config[configKey::hostName]);
|
||||
wgConfig.insert(configKey::port, config[configKey::port]);
|
||||
|
||||
bool isMultiPeer = config.contains("peers") && config["peers"].isArray()
|
||||
&& !config["peers"].toArray().isEmpty();
|
||||
|
||||
wgConfig.insert(configKey::clientIp, config[configKey::clientIp]);
|
||||
if (isMultiPeer) {
|
||||
wgConfig.insert("peers", config["peers"]);
|
||||
wgConfig.insert(configKey::allowedIps, QJsonArray{}); // required by WGConfig decoder, unused in multi-peer path
|
||||
} else {
|
||||
if (config.contains(configKey::allowedIps) && config[configKey::allowedIps].isArray()) {
|
||||
wgConfig.insert(configKey::allowedIps, config[configKey::allowedIps]);
|
||||
} else {
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(configKey::allowedIps, allowed_ips);
|
||||
}
|
||||
}
|
||||
|
||||
wgConfig.insert(configKey::clientPrivKey, config[configKey::clientPrivKey]);
|
||||
wgConfig.insert(configKey::serverPubKey, config[configKey::serverPubKey]);
|
||||
wgConfig.insert(configKey::pskKey, config[configKey::pskKey]);
|
||||
@@ -688,13 +708,6 @@ bool IosController::setupAwg()
|
||||
|
||||
wgConfig.insert(configKey::splitTunnelSites, splitTunnelSites);
|
||||
|
||||
if (config.contains(configKey::allowedIps) && config[configKey::allowedIps].isArray()) {
|
||||
wgConfig.insert(configKey::allowedIps, config[configKey::allowedIps]);
|
||||
} else {
|
||||
QJsonArray allowed_ips { "0.0.0.0/0", "::/0" };
|
||||
wgConfig.insert(configKey::allowedIps, allowed_ips);
|
||||
}
|
||||
|
||||
if (config.contains(configKey::persistentKeepAlive)) {
|
||||
wgConfig.insert(configKey::persistentKeepAlive, config[configKey::persistentKeepAlive]);
|
||||
} else {
|
||||
|
||||
@@ -7,11 +7,8 @@
|
||||
#include <net/if.h>
|
||||
|
||||
#include <QDBusVariant>
|
||||
#include <QNetworkInterface>
|
||||
#include <QTimer>
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
#include "core/networkUtilities.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
@@ -30,56 +27,24 @@ DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
|
||||
logger.debug() << "DnsUtilsLinux created.";
|
||||
|
||||
QDBusConnection conn = QDBusConnection::systemBus();
|
||||
auto* watcher = new QDBusServiceWatcher(
|
||||
DBUS_RESOLVE_SERVICE, conn,
|
||||
QDBusServiceWatcher::WatchForRegistration |
|
||||
QDBusServiceWatcher::WatchForUnregistration, this);
|
||||
|
||||
connect(watcher, &QDBusServiceWatcher::serviceRegistered,
|
||||
this, &DnsUtilsLinux::onResolverRegistered);
|
||||
connect(watcher, &QDBusServiceWatcher::serviceUnregistered,
|
||||
this, &DnsUtilsLinux::onResolverUnregistered);
|
||||
|
||||
if (conn.interface()->isServiceRegistered(DBUS_RESOLVE_SERVICE)) {
|
||||
onResolverRegistered();
|
||||
}
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::onResolverRegistered() {
|
||||
m_resolver.reset(new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
|
||||
DBUS_RESOLVE_MANAGER,
|
||||
QDBusConnection::systemBus()));
|
||||
logger.debug() << "systemd-resolved available, DNS resolver initialized";
|
||||
|
||||
if (!m_pendingIfname.isEmpty()) {
|
||||
logger.debug() << "Re-applying DNS configuration for" << m_pendingIfname;
|
||||
updateResolvers(m_pendingIfname, m_pendingResolvers);
|
||||
}
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::onResolverUnregistered() {
|
||||
logger.debug() << "systemd-resolved disappeared, dropping DNS resolver";
|
||||
m_resolver.reset();
|
||||
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
|
||||
DBUS_RESOLVE_MANAGER, conn, this);
|
||||
}
|
||||
|
||||
DnsUtilsLinux::~DnsUtilsLinux() {
|
||||
MZ_COUNT_DTOR(DnsUtilsLinux);
|
||||
|
||||
if (m_revertOnDestroy && m_resolver) {
|
||||
if (m_gatewayIfindex > 0)
|
||||
setLinkDefaultRoute(m_gatewayIfindex, true);
|
||||
for (auto iterator = m_linkDomains.constBegin();
|
||||
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(iterator.key());
|
||||
argumentList << QVariant::fromValue(iterator.value());
|
||||
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
|
||||
argumentList);
|
||||
}
|
||||
|
||||
for (auto iterator = m_linkDomains.constBegin();
|
||||
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(iterator.key());
|
||||
argumentList << QVariant::fromValue(iterator.value());
|
||||
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
|
||||
argumentList);
|
||||
}
|
||||
if (m_ifindex > 0) {
|
||||
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
|
||||
}
|
||||
if (m_ifindex > 0) {
|
||||
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
|
||||
}
|
||||
|
||||
logger.debug() << "DnsUtilsLinux destroyed.";
|
||||
@@ -87,31 +52,12 @@ DnsUtilsLinux::~DnsUtilsLinux() {
|
||||
|
||||
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
if (m_gatewayIfindex > 0) {
|
||||
setLinkDefaultRoute(m_gatewayIfindex, true);
|
||||
m_gatewayIfindex = 0;
|
||||
}
|
||||
|
||||
m_ifindex = if_nametoindex(qPrintable(ifname));
|
||||
if (m_ifindex <= 0) {
|
||||
logger.error() << "Unable to resolve ifindex for" << ifname;
|
||||
return false;
|
||||
}
|
||||
|
||||
m_pendingIfname = ifname;
|
||||
m_pendingResolvers = resolvers;
|
||||
|
||||
if (!m_resolver) {
|
||||
logger.debug() << "systemd-resolved not ready, queuing DNS configuration";
|
||||
return true;
|
||||
}
|
||||
|
||||
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
|
||||
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
|
||||
m_gatewayIfindex = gwIdx;
|
||||
setLinkDefaultRoute(gwIdx, false);
|
||||
}
|
||||
|
||||
setLinkDNS(m_ifindex, resolvers);
|
||||
setLinkDefaultRoute(m_ifindex, true);
|
||||
updateLinkDomains();
|
||||
@@ -119,15 +65,6 @@ bool DnsUtilsLinux::updateResolvers(const QString& ifname,
|
||||
}
|
||||
|
||||
bool DnsUtilsLinux::restoreResolvers() {
|
||||
m_revertOnDestroy = true;
|
||||
m_pendingIfname.clear();
|
||||
m_pendingResolvers.clear();
|
||||
|
||||
if (m_gatewayIfindex > 0) {
|
||||
setLinkDefaultRoute(m_gatewayIfindex, true);
|
||||
m_gatewayIfindex = 0;
|
||||
}
|
||||
|
||||
for (auto iterator = m_linkDomains.constBegin();
|
||||
iterator != m_linkDomains.constEnd(); ++iterator) {
|
||||
setLinkDomains(iterator.key(), iterator.value());
|
||||
@@ -135,7 +72,7 @@ bool DnsUtilsLinux::restoreResolvers() {
|
||||
m_linkDomains.clear();
|
||||
|
||||
/* Revert the VPN interface's DNS configuration */
|
||||
if (m_ifindex > 0 && m_resolver) {
|
||||
if (m_ifindex > 0) {
|
||||
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
|
||||
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||
QStringLiteral("RevertLink"), argumentList);
|
||||
@@ -153,14 +90,13 @@ bool DnsUtilsLinux::restoreResolvers() {
|
||||
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.debug() << "DBus call failed (may be transient after systemd-resolved restart)";
|
||||
logger.error() << "Error received from the DBus service";
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDNS(int ifindex,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
if (!m_resolver) return;
|
||||
QList<DnsResolver> resolverList;
|
||||
char ifnamebuf[IF_NAMESIZE];
|
||||
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||
@@ -185,7 +121,6 @@ void DnsUtilsLinux::setLinkDNS(int ifindex,
|
||||
|
||||
void DnsUtilsLinux::setLinkDomains(int ifindex,
|
||||
const QList<DnsLinkDomain>& domains) {
|
||||
if (!m_resolver) return;
|
||||
char ifnamebuf[IF_NAMESIZE];
|
||||
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||
if (ifname) {
|
||||
@@ -209,7 +144,6 @@ void DnsUtilsLinux::setLinkDomains(int ifindex,
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
|
||||
if (!m_resolver) return;
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(enable);
|
||||
@@ -222,7 +156,6 @@ void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::updateLinkDomains() {
|
||||
if (!m_resolver) return;
|
||||
/* Get the list of search domains, and remove any others that might conspire
|
||||
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
|
||||
* seem to be able to demarshall complex property types.
|
||||
@@ -241,20 +174,11 @@ void DnsUtilsLinux::updateLinkDomains() {
|
||||
|
||||
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<QVariant> reply = *call;
|
||||
call->deleteLater();
|
||||
if (reply.isError()) {
|
||||
// systemd-resolved may still be starting up after a restart — retry a few times
|
||||
if (m_ifindex > 0 && m_domainRetries++ < 5) {
|
||||
logger.debug() << "systemd-resolved not ready yet, retrying DNS setup ("
|
||||
<< m_domainRetries << "/5)";
|
||||
QTimer::singleShot(500, this, &DnsUtilsLinux::updateLinkDomains);
|
||||
} else {
|
||||
logger.warning() << "Failed to configure DNS after 5 retries";
|
||||
m_domainRetries = 0;
|
||||
}
|
||||
logger.error() << "Error retrieving the DNS domains from the DBus service";
|
||||
delete call;
|
||||
return;
|
||||
}
|
||||
m_domainRetries = 0;
|
||||
|
||||
/* Update the state of the DNS domains */
|
||||
m_linkDomains.clear();
|
||||
@@ -280,17 +204,9 @@ void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
|
||||
}
|
||||
|
||||
/* Add a root search domain for the new interface. */
|
||||
if (m_ifindex > 0) {
|
||||
setLinkDomains(m_ifindex, {root});
|
||||
|
||||
/* Disable DefaultRoute on the physical gateway so systemd-resolved
|
||||
* routes all DNS through the VPN interface. */
|
||||
const int gwIdx = NetworkUtilities::getGatewayAndIface().second.index();
|
||||
if (gwIdx > 0 && gwIdx != m_ifindex && gwIdx != m_gatewayIfindex) {
|
||||
m_gatewayIfindex = gwIdx;
|
||||
setLinkDefaultRoute(gwIdx, false);
|
||||
}
|
||||
}
|
||||
QList<DnsLinkDomain> newlist = {root};
|
||||
setLinkDomains(m_ifindex, newlist);
|
||||
delete call;
|
||||
}
|
||||
|
||||
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
|
||||
|
||||
@@ -6,12 +6,7 @@
|
||||
#define DNSUTILSLINUX_H
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QScopedPointer>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusServiceWatcher>
|
||||
#include <QHostAddress>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include "daemon/dnsutils.h"
|
||||
#include "dbustypeslinux.h"
|
||||
@@ -34,20 +29,13 @@ class DnsUtilsLinux final : public DnsUtils {
|
||||
void updateLinkDomains();
|
||||
|
||||
private slots:
|
||||
void onResolverRegistered();
|
||||
void onResolverUnregistered();
|
||||
void dnsCallCompleted(QDBusPendingCallWatcher*);
|
||||
void dnsDomainsReceived(QDBusPendingCallWatcher*);
|
||||
|
||||
private:
|
||||
int m_ifindex = 0;
|
||||
int m_gatewayIfindex = 0;
|
||||
int m_domainRetries = 0;
|
||||
bool m_revertOnDestroy = false;
|
||||
QMap<int, DnsLinkDomainList> m_linkDomains;
|
||||
QScopedPointer<QDBusInterface> m_resolver;
|
||||
QString m_pendingIfname;
|
||||
QList<QHostAddress> m_pendingResolvers;
|
||||
QDBusInterface* m_resolver = nullptr;
|
||||
};
|
||||
|
||||
#endif // DNSUTILSLINUX_H
|
||||
|
||||
@@ -5,8 +5,12 @@
|
||||
#include "iputilslinux.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/if_addr.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QHostAddress>
|
||||
@@ -71,39 +75,104 @@ bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
struct ifreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
|
||||
static bool addIPv4AddressNetlink(int ifindex, const QHostAddress& addr,
|
||||
int prefixlen) {
|
||||
int nlsock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if (nlsock < 0) return false;
|
||||
auto guard = qScopeGuard([&] { close(nlsock); });
|
||||
|
||||
// Name the interface and set family
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
ifr.ifr_addr.sa_family = AF_INET;
|
||||
char buf[512];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||
struct nlmsghdr* nlmsg = reinterpret_cast<struct nlmsghdr*>(buf);
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
|
||||
nlmsg->nlmsg_type = RTM_NEWADDR;
|
||||
nlmsg->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
|
||||
nlmsg->nlmsg_seq = 1;
|
||||
nlmsg->nlmsg_pid = 0;
|
||||
|
||||
// Create IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
struct ifaddrmsg* ifa = static_cast<struct ifaddrmsg*>(NLMSG_DATA(nlmsg));
|
||||
ifa->ifa_family = AF_INET;
|
||||
ifa->ifa_prefixlen = prefixlen;
|
||||
ifa->ifa_flags = IFA_F_PERMANENT;
|
||||
ifa->ifa_scope = RT_SCOPE_UNIVERSE;
|
||||
ifa->ifa_index = ifindex;
|
||||
|
||||
struct in_addr ip4;
|
||||
QByteArray addrBytes = addr.toString().toLocal8Bit();
|
||||
inet_pton(AF_INET, addrBytes.constData(), &ip4);
|
||||
|
||||
auto appendAttr = [](struct nlmsghdr* nlmsg, size_t maxlen, int type,
|
||||
const void* data, size_t len) {
|
||||
size_t newlen = NLMSG_ALIGN(nlmsg->nlmsg_len) + RTA_SPACE(len);
|
||||
if (newlen > maxlen) return;
|
||||
char* p = reinterpret_cast<char*>(nlmsg) + NLMSG_ALIGN(nlmsg->nlmsg_len);
|
||||
struct rtattr* rta = reinterpret_cast<struct rtattr*>(p);
|
||||
rta->rta_type = type;
|
||||
rta->rta_len = RTA_LENGTH(len);
|
||||
memcpy(RTA_DATA(rta), data, len);
|
||||
nlmsg->nlmsg_len = newlen;
|
||||
};
|
||||
|
||||
appendAttr(nlmsg, sizeof(buf), IFA_LOCAL, &ip4, sizeof(ip4));
|
||||
appendAttr(nlmsg, sizeof(buf), IFA_ADDRESS, &ip4, sizeof(ip4));
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
|
||||
if (sendto(nlsock, buf, nlmsg->nlmsg_len, 0,
|
||||
reinterpret_cast<struct sockaddr*>(&nladdr),
|
||||
sizeof(nladdr)) < 0) {
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
char ackbuf[1024];
|
||||
ssize_t acklen = recv(nlsock, ackbuf, sizeof(ackbuf), 0);
|
||||
if (acklen >= static_cast<ssize_t>(sizeof(struct nlmsghdr))) {
|
||||
struct nlmsghdr* ackmsg = reinterpret_cast<struct nlmsghdr*>(ackbuf);
|
||||
if (ackmsg->nlmsg_type == NLMSG_ERROR) {
|
||||
struct nlmsgerr* err = static_cast<struct nlmsgerr*>(NLMSG_DATA(ackmsg));
|
||||
if (err->error != 0) {
|
||||
errno = -err->error;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
if (config.m_deviceIpv4Address.isEmpty()) return true;
|
||||
|
||||
int ifindex = if_nametoindex(WG_INTERFACE);
|
||||
if (ifindex == 0) {
|
||||
logger.error() << "Failed to get ifindex for" << WG_INTERFACE;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
const QStringList addresses =
|
||||
config.m_deviceIpv4Address.split(',', Qt::SkipEmptyParts);
|
||||
for (const QString& entry : addresses) {
|
||||
QPair<QHostAddress, int> parsed =
|
||||
QHostAddress::parseSubnet(entry.trimmed());
|
||||
if (parsed.first.isNull()) {
|
||||
logger.warning() << "Failed to parse IPv4 address:" << entry.trimmed();
|
||||
continue;
|
||||
}
|
||||
if (!addIPv4AddressNetlink(ifindex, parsed.first, parsed.second)) {
|
||||
logger.error() << "Failed to add IPv4" << parsed.first.toString() << "/"
|
||||
<< parsed.second << ":" << strerror(errno);
|
||||
} else {
|
||||
logger.debug() << "Added IPv4" << parsed.first.toString() << "/"
|
||||
<< parsed.second << "to" << WG_INTERFACE;
|
||||
ok = true;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||
// Set up the ifr and the companion ifr6
|
||||
struct in6_ifreq ifr6;
|
||||
|
||||
@@ -454,33 +454,16 @@ void LinuxFirewall::updateDNSServers(const QStringList& servers)
|
||||
static QStringList existingServers {};
|
||||
|
||||
existingServers = servers;
|
||||
const QString chain = QStringLiteral("%1.320.allowDNS").arg(kAnchorName);
|
||||
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
|
||||
const QStringList ifaces = {
|
||||
QStringLiteral("amn0+"), QStringLiteral("tun0+"), QStringLiteral("tun2+")
|
||||
};
|
||||
for (const QString& server : servers) {
|
||||
for (const QString& iface : ifaces) {
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
|
||||
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("udp"),
|
||||
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-o"), iface,
|
||||
QStringLiteral("-d"), server, QStringLiteral("-p"), QStringLiteral("tcp"),
|
||||
QStringLiteral("--dport"), QStringLiteral("53"), QStringLiteral("-j"), QStringLiteral("ACCEPT")});
|
||||
}
|
||||
}
|
||||
execute(QStringLiteral("iptables -F %1.320.allowDNS").arg(kAnchorName));
|
||||
for (const QString& rule : getDNSRules(servers))
|
||||
execute(QStringLiteral("iptables -A %1.320.allowDNS %2").arg(kAnchorName, rule));
|
||||
}
|
||||
|
||||
void LinuxFirewall::updateAllowNets(const QStringList& servers)
|
||||
{
|
||||
const QString chain = QStringLiteral("%1.110.allowNets").arg(kAnchorName);
|
||||
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
|
||||
for (const QString& server : servers)
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
|
||||
QStringLiteral("-j"), QStringLiteral("ACCEPT")});
|
||||
execute(QStringLiteral("iptables -F %1.110.allowNets").arg(kAnchorName));
|
||||
for (const QString& rule : getAllowRule(servers))
|
||||
execute(QStringLiteral("iptables -A %1.110.allowNets %2").arg(kAnchorName, rule));
|
||||
}
|
||||
|
||||
void LinuxFirewall::updateBlockNets(const QStringList& servers)
|
||||
@@ -488,12 +471,9 @@ void LinuxFirewall::updateBlockNets(const QStringList& servers)
|
||||
static QStringList existingServers {};
|
||||
|
||||
existingServers = servers;
|
||||
const QString chain = QStringLiteral("%1.120.blockNets").arg(kAnchorName);
|
||||
executeIptables(QStringLiteral("iptables"), {QStringLiteral("-F"), chain});
|
||||
for (const QString& server : servers)
|
||||
executeIptables(QStringLiteral("iptables"),
|
||||
{QStringLiteral("-A"), chain, QStringLiteral("-d"), server,
|
||||
QStringLiteral("-j"), QStringLiteral("REJECT")});
|
||||
execute(QStringLiteral("iptables -F %1.120.blockNets").arg(kAnchorName));
|
||||
for (const QString& rule : getBlockRule(servers))
|
||||
execute(QStringLiteral("iptables -A %1.120.blockNets %2").arg(kAnchorName, rule));
|
||||
}
|
||||
|
||||
int waitForExitCode(QProcess& process)
|
||||
@@ -526,24 +506,6 @@ int LinuxFirewall::execute(const QString &command, bool ignoreErrors)
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
int LinuxFirewall::executeIptables(const QString &program, const QStringList &args, bool ignoreErrors)
|
||||
{
|
||||
QProcess p;
|
||||
p.start(program, args, QProcess::ReadOnly);
|
||||
p.closeWriteChannel();
|
||||
|
||||
int exitCode = waitForExitCode(p);
|
||||
auto out = p.readAllStandardOutput().trimmed();
|
||||
auto err = p.readAllStandardError().trimmed();
|
||||
if ((exitCode != 0 || !err.isEmpty()) && !ignoreErrors)
|
||||
logger.warning() << "(" << exitCode << ") $ " << program << args.join(QLatin1Char(' '));
|
||||
if (!out.isEmpty())
|
||||
logger.info() << out;
|
||||
if (!err.isEmpty())
|
||||
logger.warning() << err;
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
void LinuxFirewall::setupTrafficSplitting()
|
||||
{
|
||||
auto cGroupDir = "/sys/fs/cgroup/net_cls/" BRAND_CODE "vpnexclusions/";
|
||||
|
||||
@@ -85,7 +85,6 @@ private:
|
||||
static void setupTrafficSplitting();
|
||||
static void teardownTrafficSplitting();
|
||||
static int execute(const QString& command, bool ignoreErrors = false);
|
||||
static int executeIptables(const QString& program, const QStringList& args, bool ignoreErrors = false);
|
||||
private:
|
||||
// Chain names
|
||||
static QString kOutputChain, kRootChain, kPostRoutingChain, kPreRoutingChain;
|
||||
|
||||
@@ -230,26 +230,55 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
const QList<IPAddress>& primaryIPs = config.m_primaryPeerAllowedIPRanges.isEmpty()
|
||||
? config.m_allowedIPAddressRanges
|
||||
: config.m_primaryPeerAllowedIPRanges;
|
||||
for (const IPAddress& ip : primaryIPs) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
// Exclude the server address, except for multihop exit servers.
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) &&
|
||||
(m_rtmonitor != nullptr)) {
|
||||
if (!config.m_serverIpv4AddrIn.isEmpty() &&
|
||||
!m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn))) {
|
||||
logger.error() << "No gateway — cannot add server exclusion route";
|
||||
return false;
|
||||
}
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv4AddrIn));
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(config.m_serverIpv6AddrIn));
|
||||
}
|
||||
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
return false;
|
||||
}
|
||||
return (err == 0);
|
||||
|
||||
for (const InterfaceConfig::AdditionalPeerConfig& peer : config.m_additionalPeers) {
|
||||
QByteArray pubKey = QByteArray::fromBase64(peer.m_serverPublicKey.toUtf8());
|
||||
QByteArray pskKey = QByteArray::fromBase64(peer.m_serverPskKey.toUtf8());
|
||||
|
||||
QString peerMsg;
|
||||
QTextStream peerOut(&peerMsg);
|
||||
peerOut << "set=1\n";
|
||||
peerOut << "public_key=" << QString(pubKey.toHex()) << "\n";
|
||||
if (!peer.m_serverPskKey.isEmpty()) {
|
||||
peerOut << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
peerOut << "endpoint=" << peer.m_serverIpv4AddrIn << ":" << peer.m_serverPort << "\n";
|
||||
peerOut << "replace_allowed_ips=true\n";
|
||||
peerOut << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : peer.m_allowedIPAddressRanges) {
|
||||
peerOut << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) && m_rtmonitor) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(peer.m_serverIpv4AddrIn));
|
||||
}
|
||||
|
||||
int peerErr = uapiErrno(uapiCommand(peerMsg));
|
||||
if (peerErr != 0) {
|
||||
logger.error() << "Additional peer configuration failed:" << strerror(peerErr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deletePeer(const InterfaceConfig& config) {
|
||||
|
||||
@@ -80,7 +80,9 @@ bool IPUtilsMacos::setMTUAndUp(const InterfaceConfig& config) {
|
||||
}
|
||||
|
||||
bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
if (config.m_deviceIpv4Address.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
QString ifname = MacOSDaemon::instance()->m_wgutils->interfaceName();
|
||||
struct ifaliasreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifra_addr;
|
||||
@@ -91,25 +93,28 @@ bool IPUtilsMacos::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
memset(&ifr, 0, sizeof(ifr));
|
||||
strncpy(ifr.ifra_name, qPrintable(ifname), IFNAMSIZ);
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
// Extract the host IP from CIDR notation (e.g. "10.8.0.2/24" → "10.8.0.2").
|
||||
// parseSubnet() zeroes host bits so we split manually to preserve the host address.
|
||||
QByteArray _deviceAddr = config.m_deviceIpv4Address.split('/').first().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
ifrAddr->sin_family = AF_INET;
|
||||
ifrAddr->sin_len = sizeof(struct sockaddr_in);
|
||||
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||
if (inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr) != 1) {
|
||||
logger.error() << "Failed to parse IPv4 address:" << deviceAddr;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Set the netmask to /32
|
||||
ifrMask->sin_family = AF_INET;
|
||||
ifrMask->sin_len = sizeof(struct sockaddr_in);
|
||||
memset(&ifrMask->sin_addr, 0xff, sizeof(ifrMask->sin_addr));
|
||||
|
||||
// Set the broadcast address.
|
||||
// For P2P (utun) interfaces, ifra_broadaddr is the destination address.
|
||||
// Set it equal to the local address to create only a host route (not a network
|
||||
// route that would cause a routing loop).
|
||||
ifrBcast->sin_family = AF_INET;
|
||||
ifrBcast->sin_len = sizeof(struct sockaddr_in);
|
||||
ifrBcast->sin_addr.s_addr =
|
||||
(ifrAddr->sin_addr.s_addr | ~ifrMask->sin_addr.s_addr);
|
||||
ifrBcast->sin_addr.s_addr = ifrAddr->sin_addr.s_addr;
|
||||
|
||||
// Create an IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
|
||||
@@ -230,7 +230,11 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
// For multi-peer use only the primary peer's own IPs to avoid routing trie conflicts.
|
||||
const QList<IPAddress>& primaryIPs = config.m_primaryPeerAllowedIPRanges.isEmpty()
|
||||
? config.m_allowedIPAddressRanges
|
||||
: config.m_primaryPeerAllowedIPRanges;
|
||||
for (const IPAddress& ip : primaryIPs) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
@@ -244,8 +248,38 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
int err = uapiErrno(uapiCommand(message));
|
||||
if (err != 0) {
|
||||
logger.error() << "Peer configuration failed:" << strerror(err);
|
||||
return false;
|
||||
}
|
||||
return (err == 0);
|
||||
|
||||
for (const InterfaceConfig::AdditionalPeerConfig& peer : config.m_additionalPeers) {
|
||||
QByteArray pubKey = QByteArray::fromBase64(peer.m_serverPublicKey.toUtf8());
|
||||
QByteArray pskKey = QByteArray::fromBase64(peer.m_serverPskKey.toUtf8());
|
||||
|
||||
QString peerMsg;
|
||||
QTextStream peerOut(&peerMsg);
|
||||
peerOut << "set=1\n";
|
||||
peerOut << "public_key=" << QString(pubKey.toHex()) << "\n";
|
||||
if (!peer.m_serverPskKey.isEmpty()) {
|
||||
peerOut << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
peerOut << "endpoint=" << peer.m_serverIpv4AddrIn << ":" << peer.m_serverPort << "\n";
|
||||
peerOut << "replace_allowed_ips=true\n";
|
||||
peerOut << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : peer.m_allowedIPAddressRanges) {
|
||||
peerOut << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
if ((config.m_hopType != InterfaceConfig::MultiHopExit) && m_rtmonitor) {
|
||||
m_rtmonitor->addExclusionRoute(IPAddress(peer.m_serverIpv4AddrIn));
|
||||
}
|
||||
|
||||
int peerErr = uapiErrno(uapiCommand(peerMsg));
|
||||
if (peerErr != 0) {
|
||||
logger.error() << "Additional peer configuration failed:" << strerror(peerErr);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsMacos::deletePeer(const InterfaceConfig& config) {
|
||||
|
||||
@@ -181,7 +181,10 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
out << "replace_allowed_ips=true\n";
|
||||
out << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : config.m_allowedIPAddressRanges) {
|
||||
const QList<IPAddress>& primaryIPs = config.m_primaryPeerAllowedIPRanges.isEmpty()
|
||||
? config.m_allowedIPAddressRanges
|
||||
: config.m_primaryPeerAllowedIPRanges;
|
||||
for (const IPAddress& ip : primaryIPs) {
|
||||
out << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
@@ -193,6 +196,33 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
|
||||
QString reply = m_tunnel.uapiCommand(message);
|
||||
logger.debug() << "DATA:" << reply;
|
||||
|
||||
for (const InterfaceConfig::AdditionalPeerConfig& peer : config.m_additionalPeers) {
|
||||
QByteArray pubKey = QByteArray::fromBase64(peer.m_serverPublicKey.toUtf8());
|
||||
QByteArray pskKey = QByteArray::fromBase64(peer.m_serverPskKey.toUtf8());
|
||||
|
||||
QString peerMsg;
|
||||
QTextStream peerOut(&peerMsg);
|
||||
peerOut << "set=1\n";
|
||||
peerOut << "public_key=" << QString(pubKey.toHex()) << "\n";
|
||||
if (!peer.m_serverPskKey.isEmpty()) {
|
||||
peerOut << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
peerOut << "endpoint=" << peer.m_serverIpv4AddrIn << ":" << peer.m_serverPort << "\n";
|
||||
peerOut << "replace_allowed_ips=true\n";
|
||||
peerOut << "persistent_keepalive_interval=" << WG_KEEPALIVE_PERIOD << "\n";
|
||||
for (const IPAddress& ip : peer.m_allowedIPAddressRanges) {
|
||||
peerOut << "allowed_ip=" << ip.toString() << "\n";
|
||||
}
|
||||
|
||||
if (m_routeMonitor && config.m_hopType != InterfaceConfig::MultiHopExit) {
|
||||
m_routeMonitor->addExclusionRoute(IPAddress(peer.m_serverIpv4AddrIn));
|
||||
}
|
||||
|
||||
QString peerReply = m_tunnel.uapiCommand(peerMsg);
|
||||
logger.debug() << "Additional peer DATA:" << peerReply;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
if which apt-get > /dev/null 2>&1 || command -v apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1 || command -v dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
|
||||
elif which yum > /dev/null 2>&1 || command -v yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which zypper > /dev/null 2>&1 || command -v zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
|
||||
elif which pacman > /dev/null 2>&1 || command -v pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1;\
|
||||
fi;\
|
||||
if sudo -n which $LOCK_CMD > /dev/null 2>&1 || command -v $LOCK_CMD > /dev/null 2>&1; then sudo -n $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
|
||||
if which apt-get > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/dpkg/lock-frontend";\
|
||||
elif which dnf > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/cache/dnf/* /var/run/dnf/* /var/lib/dnf/* /var/lib/rpm/*";\
|
||||
elif which yum > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/yum.pid";\
|
||||
elif which zypper > /dev/null 2>&1; then LOCK_CMD="cat"; LOCK_FILE="/var/run/zypp.pid";\
|
||||
elif which pacman > /dev/null 2>&1; then LOCK_CMD="fuser"; LOCK_FILE="/var/lib/pacman/db.lck";\
|
||||
else echo "Packet manager not found"; echo "Internal error"; exit 1; fi;\
|
||||
if command -v $LOCK_CMD > /dev/null 2>&1; then sudo $LOCK_CMD $LOCK_FILE 2>/dev/null; else echo "$LOCK_CMD not installed"; fi
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then opt="--version";\
|
||||
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then opt="--version";\
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); opt="--version";\
|
||||
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); opt="--version";\
|
||||
elif which yum > /dev/null 2>&1; then pm=$(which yum); opt="--version";\
|
||||
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); opt="--version";\
|
||||
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); opt="--version";\
|
||||
else pm="uname"; opt="-a";\
|
||||
fi;\
|
||||
CUR_USER=$(whoami 2>/dev/null || echo $HOME | sed 's/.*\///');\
|
||||
|
||||
@@ -1,34 +1,25 @@
|
||||
if pm=$(which apt-get 2>/dev/null || command -v apt-get 2>/dev/null); then silent_inst="-yq install --install-recommends"; what_pkg="-s install"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
|
||||
elif pm=$(which dnf 2>/dev/null || command -v dnf 2>/dev/null); then silent_inst="-yq install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
|
||||
elif pm=$(which yum 2>/dev/null || command -v yum 2>/dev/null); then silent_inst="-y -q install"; what_pkg="--assumeno install --setopt=tsflags=test"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
|
||||
elif pm=$(which zypper 2>/dev/null || command -v zypper 2>/dev/null); then silent_inst="-nq install"; what_pkg="--dry-run install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="suse";\
|
||||
elif pm=$(which pacman 2>/dev/null || command -v pacman 2>/dev/null); then silent_inst="-S --noconfirm --noprogressbar --quiet"; what_pkg="-Sp"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
|
||||
fi;\
|
||||
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, What pkg command: $what_pkg, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg, Language: $LANG";\
|
||||
echo $LANG | grep -qE '^(en_US.UTF-8|C.UTF-8|C)$' || export LC_ALL=C;\
|
||||
if which apt-get > /dev/null 2>&1; then pm=$(which apt-get); silent_inst="-yq install --install-recommends"; check_pkgs="-yq update"; docker_pkg="docker.io"; dist="debian";\
|
||||
elif which dnf > /dev/null 2>&1; then pm=$(which dnf); silent_inst="-yq install"; check_pkgs="-yq check-update"; docker_pkg="docker"; dist="fedora";\
|
||||
elif which yum > /dev/null 2>&1; then pm=$(which yum); silent_inst="-y -q install"; check_pkgs="-y -q check-update"; docker_pkg="docker"; dist="centos";\
|
||||
elif which zypper > /dev/null 2>&1; then pm=$(which zypper); silent_inst="-nq install"; check_pkgs="-nq refresh"; docker_pkg="docker"; dist="opensuse";\
|
||||
elif which pacman > /dev/null 2>&1; then pm=$(which pacman); silent_inst="-S --noconfirm --noprogressbar --quiet"; check_pkgs="-Sup"; docker_pkg="docker"; dist="archlinux";\
|
||||
else echo "Packet manager not found"; exit 1; fi;\
|
||||
echo "Dist: $dist, Packet manager: $pm, Install command: $silent_inst, Check pkgs command: $check_pkgs, Docker pkg: $docker_pkg";\
|
||||
if [ "$dist" = "debian" ]; then export DEBIAN_FRONTEND=noninteractive; fi;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then $pm $check_pkgs; $pm $silent_inst sudo; fi;\
|
||||
if ! sudo -n sh -c 'command -v which > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst which; fi;\
|
||||
if ! sudo -n sh -c 'command -v fuser > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst psmisc; fi;\
|
||||
if ! sudo -n sh -c 'command -v lsof > /dev/null 2>&1'; then sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst lsof; fi;\
|
||||
if ! sudo -n sh -c 'command -v docker > /dev/null 2>&1'; then \
|
||||
sudo -n $pm $check_pkgs;\
|
||||
if ! sudo -n $pm $what_pkg $docker_pkg 2>/dev/null | grep -qi podman; then \
|
||||
sudo -n $pm $silent_inst $docker_pkg;\
|
||||
sleep 5; sudo -n systemctl enable --now docker; sleep 5;\
|
||||
else \
|
||||
echo "Container runtime is not supported";\
|
||||
exit 1;\
|
||||
fi;\
|
||||
if ! command -v fuser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst psmisc; fi;\
|
||||
if ! command -v lsof > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst lsof; fi;\
|
||||
if ! command -v docker > /dev/null 2>&1; then \
|
||||
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||
sleep 5; sudo systemctl enable --now docker; sleep 5;\
|
||||
fi;\
|
||||
if [ "$(sudo -n cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
|
||||
if ! sudo -n sh -c 'command -v apparmor_parser > /dev/null 2>&1'; then \
|
||||
sudo -n $pm $check_pkgs; sudo -n $pm $silent_inst apparmor;\
|
||||
fi;\
|
||||
if [ "$(cat /sys/module/apparmor/parameters/enabled 2>/dev/null)" = "Y" ]; then \
|
||||
if ! command -v apparmor_parser > /dev/null 2>&1; then sudo $pm $check_pkgs; sudo $pm $silent_inst apparmor; fi;\
|
||||
fi;\
|
||||
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then \
|
||||
sleep 5; sudo -n systemctl start docker; sleep 5;\
|
||||
if [ "$(sudo -n systemctl is-active docker)" != "active" ]; then echo "Container runtime service not running"; fi;\
|
||||
if [ "$(systemctl is-active docker)" != "active" ]; then \
|
||||
sudo $pm $check_pkgs; sudo $pm $silent_inst $docker_pkg;\
|
||||
sleep 5; sudo systemctl start docker; sleep 5;\
|
||||
fi;\
|
||||
sudo -n docker --version || docker --version;\
|
||||
if ! command -v sudo > /dev/null 2>&1; then echo "Failed to install sudo, command not found"; exit 1; fi;\
|
||||
docker --version;\
|
||||
uname -sr
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker stop;\
|
||||
sudo docker ps -a | grep amnezia | awk '{print $1}' | xargs sudo docker rm -fv;\
|
||||
sudo docker images -a --format table | grep amnezia | awk '{print $3, $1 ":" $2}' | xargs sudo docker rmi;\
|
||||
sudo docker volume ls --format '{{.Name}}' | grep '^amnezia-' | xargs -r sudo docker volume rm -f;\
|
||||
sudo docker network ls | grep amnezia-dns-net | awk '{print $1}' | xargs sudo docker network rm;\
|
||||
sudo rm -frd /opt/amnezia
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
sudo docker stop $CONTAINER_NAME;\
|
||||
sudo docker rm -fv $CONTAINER_NAME;\
|
||||
sudo docker rmi $CONTAINER_NAME;
|
||||
sudo docker rmi $CONTAINER_NAME;\
|
||||
test "$REMOVE_CONTAINER_DATA" = "1" && sudo docker volume rm -f ${CONTAINER_NAME}-data 2>/dev/null || true
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "subscriptionUiController.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/configurators/wireguardConfigurator.h"
|
||||
#include "core/utils/serverConfigUtils.h"
|
||||
@@ -475,7 +479,8 @@ bool SubscriptionUiController::deactivateExternalDevice(const QString &serverId,
|
||||
void SubscriptionUiController::validateConfig()
|
||||
{
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
if (!serverId.isEmpty() && m_serversController->isLegacyApiV1Server(serverId)) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
emit configValidated(false);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#include "amneziaApplication.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "core/models/containerConfig.h"
|
||||
#include "core/utils/containerEnum.h"
|
||||
|
||||
ConnectionUiController::ConnectionUiController(ConnectionController* connectionController,
|
||||
ServersController* serversController,
|
||||
@@ -35,7 +33,7 @@ void ConnectionUiController::openConnection()
|
||||
ErrorCode errorCode = m_connectionController->openConnection(serverId);
|
||||
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -132,36 +130,10 @@ void ConnectionUiController::toggleConnection()
|
||||
} else if (isConnected()) {
|
||||
closeConnection();
|
||||
} else {
|
||||
const QString serverId = m_serversController->getDefaultServerId();
|
||||
if (serverId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ErrorCode errorCode = m_connectionController->isConnectionSupported(serverId);
|
||||
if (errorCode != ErrorCode::NoError) {
|
||||
notifyConnectionBlocked(errorCode);
|
||||
return;
|
||||
}
|
||||
|
||||
emit prepareConfig();
|
||||
}
|
||||
}
|
||||
|
||||
void ConnectionUiController::notifyConnectionBlocked(ErrorCode errorCode)
|
||||
{
|
||||
if (errorCode == ErrorCode::LegacyApiV1NotSupportedError) {
|
||||
emit unsupportedConnectDrawerRequested();
|
||||
return;
|
||||
}
|
||||
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
return;
|
||||
}
|
||||
|
||||
emit connectionErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isConnectionInProgress() const
|
||||
{
|
||||
return m_isConnectionInProgress;
|
||||
@@ -171,32 +143,3 @@ bool ConnectionUiController::isConnected() const
|
||||
{
|
||||
return m_isConnected;
|
||||
}
|
||||
|
||||
bool ConnectionUiController::isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex,
|
||||
const QString &clientId) const
|
||||
{
|
||||
if (clientId.isEmpty() || (!isConnected() && !isConnectionInProgress())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_serversController->getDefaultServerId() != serverId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (static_cast<int>(m_serversController->getDefaultContainer(serverId)) != containerIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto adminConfig = m_serversController->selfHostedAdminConfig(serverId);
|
||||
if (!adminConfig.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString connectionClientId =
|
||||
adminConfig->containerConfig(static_cast<DockerContainer>(containerIndex)).protocolConfig.clientId();
|
||||
if (connectionClientId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return connectionClientId == clientId || connectionClientId.contains(clientId);
|
||||
}
|
||||
|
||||
@@ -35,8 +35,6 @@ public slots:
|
||||
void openConnection();
|
||||
void closeConnection();
|
||||
|
||||
bool isRevokeBlockedDuringActiveConnection(const QString &serverId, int containerIndex, const QString &clientId) const;
|
||||
|
||||
ErrorCode getLastConnectionError();
|
||||
void onConnectionStateChanged(Vpn::ConnectionState state);
|
||||
|
||||
@@ -50,12 +48,9 @@ signals:
|
||||
void connectButtonClicked();
|
||||
void preparingConfig();
|
||||
void prepareConfig();
|
||||
void unsupportedConnectDrawerRequested();
|
||||
void noInstalledContainers();
|
||||
|
||||
private:
|
||||
Vpn::ConnectionState getCurrentConnectionState();
|
||||
void notifyConnectionBlocked(ErrorCode errorCode);
|
||||
|
||||
ConnectionController* m_connectionController;
|
||||
ServersController* m_serversController;
|
||||
|
||||
@@ -128,11 +128,6 @@ void PageController::showOnStartup()
|
||||
}
|
||||
}
|
||||
|
||||
bool PageController::shouldStartMinimized() const
|
||||
{
|
||||
return m_settingsController->isStartMinimizedEnabled();
|
||||
}
|
||||
|
||||
bool PageController::isTriggeredByConnectButton()
|
||||
{
|
||||
return m_isTriggeredByConnectButton;
|
||||
|
||||
@@ -123,7 +123,6 @@ public slots:
|
||||
void updateNavigationBarColor(const int color);
|
||||
|
||||
void showOnStartup();
|
||||
bool shouldStartMinimized() const;
|
||||
|
||||
bool isTriggeredByConnectButton();
|
||||
void setTriggeredByConnectButton(bool trigger);
|
||||
|
||||
@@ -9,13 +9,6 @@ ExportUiController::ExportUiController(ExportController* exportController, QObje
|
||||
: QObject(parent),
|
||||
m_exportController(exportController)
|
||||
{
|
||||
connect(m_exportController, &ExportController::revokeFinished, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit revokeConfigFinished();
|
||||
} else {
|
||||
emit exportErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ExportUiController::generateFullAccessConfig(const QString &serverId)
|
||||
@@ -99,6 +92,7 @@ void ExportUiController::updateClientManagementModel(const QString &serverId, in
|
||||
void ExportUiController::revokeConfig(int row, const QString &serverId, int containerIndex)
|
||||
{
|
||||
m_exportController->revokeConfig(row, serverId, containerIndex);
|
||||
emit revokeConfigFinished();
|
||||
}
|
||||
|
||||
void ExportUiController::renameClient(int row, const QString &clientName, const QString &serverId, int containerIndex)
|
||||
|
||||
@@ -75,7 +75,13 @@ InstallUiController::InstallUiController(InstallController *installController,
|
||||
m_connectionController(connectionController)
|
||||
{
|
||||
connect(m_installController, &InstallController::configValidated, this, &InstallUiController::configValidated);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, &InstallUiController::installationErrorOccurred);
|
||||
connect(m_installController, &InstallController::validationErrorOccurred, this, [this](ErrorCode errorCode) {
|
||||
if (errorCode == ErrorCode::NoInstalledContainersError) {
|
||||
emit noInstalledContainers();
|
||||
} else {
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
InstallUiController::~InstallUiController()
|
||||
@@ -211,13 +217,15 @@ void InstallUiController::scanServerForInstalledContainers(const QString &server
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig)
|
||||
void InstallUiController::updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
containerConfig.container = container;
|
||||
|
||||
|
||||
switch (protocolType) {
|
||||
case Proto::Awg: {
|
||||
containerConfig.protocolConfig = m_awgConfigModel->getProtocolConfig();
|
||||
@@ -263,41 +271,6 @@ bool InstallUiController::buildContainerConfigFromModel(int containerIndex, int
|
||||
}
|
||||
#endif
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstallUiController::updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateClientConfig(serverId, container, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
m_protocolModel->updateModel(updatedConfig);
|
||||
updateProtocolConfigModel(serverId, static_cast<int>(container), static_cast<int>(protocolType));
|
||||
emit updateContainerFinished(tr("Settings updated successfully"), closePage);
|
||||
return;
|
||||
}
|
||||
|
||||
emit installationErrorOccurred(errorCode);
|
||||
}
|
||||
|
||||
void InstallUiController::updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage)
|
||||
{
|
||||
DockerContainer container = static_cast<DockerContainer>(containerIndex);
|
||||
Proto protocolType = static_cast<Proto>(protocolIndex);
|
||||
|
||||
ContainerConfig containerConfig;
|
||||
if (!buildContainerConfigFromModel(containerIndex, protocolIndex, containerConfig)) {
|
||||
return;
|
||||
}
|
||||
ContainerConfig oldContainerConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
@@ -306,17 +279,14 @@ void InstallUiController::updateServerConfig(const QString &serverId, int contai
|
||||
|| container == DockerContainer::Xray || container == DockerContainer::SSXray;
|
||||
|
||||
if (asyncUpdate) {
|
||||
const bool emitBusy = container == DockerContainer::MtProxy || container == DockerContainer::Telemt;
|
||||
if (emitBusy)
|
||||
emit serverIsBusy(true);
|
||||
emit serverIsBusy(true);
|
||||
auto *watcher = new QFutureWatcher<ErrorCode>(this);
|
||||
const Proto protocolTypeCopy = protocolType;
|
||||
QObject::connect(watcher, &QFutureWatcher<ErrorCode>::finished, this,
|
||||
[this, watcher, serverId, container, closePage, protocolTypeCopy, emitBusy]() {
|
||||
[this, watcher, serverId, container, closePage, protocolTypeCopy]() {
|
||||
const ErrorCode errorCode = watcher->result();
|
||||
watcher->deleteLater();
|
||||
if (emitBusy)
|
||||
emit serverIsBusy(false);
|
||||
emit serverIsBusy(false);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
const ContainerConfig updatedConfig =
|
||||
@@ -335,13 +305,13 @@ void InstallUiController::updateServerConfig(const QString &serverId, int contai
|
||||
QFuture<ErrorCode> future =
|
||||
QtConcurrent::run([installController, serverId, container, oldConfigCopy,
|
||||
newConfigCopy]() mutable -> ErrorCode {
|
||||
return installController->updateServerConfig(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
return installController->updateContainer(serverId, container, oldConfigCopy, newConfigCopy);
|
||||
});
|
||||
watcher->setFuture(future);
|
||||
return;
|
||||
}
|
||||
|
||||
ErrorCode errorCode = m_installController->updateServerConfig(serverId, container, oldContainerConfig, containerConfig);
|
||||
ErrorCode errorCode = m_installController->updateContainer(serverId, container, oldContainerConfig, containerConfig);
|
||||
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
ContainerConfig updatedConfig = m_serversController->getContainerConfig(serverId, container);
|
||||
|
||||
@@ -64,8 +64,7 @@ public slots:
|
||||
|
||||
void scanServerForInstalledContainers(const QString &serverId);
|
||||
|
||||
void updateServerConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateClientConfig(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
void updateContainer(const QString &serverId, int containerIndex, int protocolIndex, bool closePage = true);
|
||||
|
||||
void removeServer(const QString &serverId);
|
||||
void rebootServer(const QString &serverId);
|
||||
@@ -133,6 +132,7 @@ signals:
|
||||
void cachedProfileCleared(const QString &message);
|
||||
void apiConfigRemoved(const QString &message);
|
||||
|
||||
void noInstalledContainers();
|
||||
void configValidated(bool isValid);
|
||||
|
||||
private:
|
||||
@@ -162,8 +162,6 @@ private:
|
||||
QString m_privateKeyPassphrase;
|
||||
|
||||
void updateProtocolConfigModel(const QString &serverId, int containerIndex, int protocolIndex);
|
||||
|
||||
bool buildContainerConfigFromModel(int containerIndex, int protocolIndex, ContainerConfig &containerConfig);
|
||||
};
|
||||
|
||||
#endif // INSTALLUICONTROLLER_H
|
||||
|
||||
@@ -156,17 +156,7 @@ void ServersUiController::updateModel()
|
||||
|
||||
m_serversModel->updateModel(m_orderedServerDescriptions, defaultServerId);
|
||||
|
||||
if (!m_processedServerId.isEmpty()) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
}
|
||||
}
|
||||
updateContainersModel();
|
||||
updateDefaultServerContainersModel();
|
||||
|
||||
if (hadServersFromGatewayBefore != hasServersFromGatewayNow) {
|
||||
@@ -360,14 +350,19 @@ void ServersUiController::setProcessedServerId(const QString &serverId)
|
||||
m_processedServerId = normalizedServerId;
|
||||
|
||||
if (newIndex >= 0) {
|
||||
if (isServerFromApi(m_processedServerId)) {
|
||||
const auto &description = serverDescriptionById(m_processedServerId);
|
||||
if (description.isApiV2 && description.isCountrySelectionAvailable
|
||||
&& !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
updateContainersModel();
|
||||
|
||||
for (const auto &description : m_orderedServerDescriptions) {
|
||||
if (description.serverId != normalizedServerId) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
updateContainersModel();
|
||||
if (description.isApiV2) {
|
||||
if (description.isCountrySelectionAvailable && !description.apiAvailableCountries.isEmpty()) {
|
||||
emit updateApiCountryModel();
|
||||
}
|
||||
emit updateApiServicesModel();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ signals:
|
||||
void processedContainerIndexChanged(int index);
|
||||
void hasServersFromGatewayApiChanged();
|
||||
void updateApiCountryModel();
|
||||
void updateApiServicesModel();
|
||||
|
||||
public:
|
||||
void updateModel();
|
||||
|
||||
@@ -22,10 +22,12 @@
|
||||
|
||||
SettingsUiController::SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent)
|
||||
: QObject(parent),
|
||||
m_settingsController(settingsController),
|
||||
m_serversController(serversController)
|
||||
m_serversController(serversController),
|
||||
m_languageUiController(languageUiController)
|
||||
{
|
||||
#ifdef Q_OS_ANDROID
|
||||
connect(AndroidController::instance(), &AndroidController::notificationStateChanged, this, &SettingsUiController::onNotificationStateChanged);
|
||||
@@ -155,13 +157,13 @@ void SettingsUiController::restoreAppConfigFromData(const QByteArray &data)
|
||||
{
|
||||
ErrorCode errorCode = m_settingsController->restoreAppConfigFromData(data);
|
||||
if (errorCode == ErrorCode::NoError) {
|
||||
emit appLanguageChanged();
|
||||
emit appLanguageChanged(
|
||||
static_cast<LanguageSettings::AvailableLanguageEnum>(m_languageUiController->getCurrentLanguageIndex()));
|
||||
|
||||
bool amneziaDnsEnabled = m_settingsController->isAmneziaDnsEnabled();
|
||||
emit amneziaDnsToggled(amneziaDnsEnabled);
|
||||
|
||||
emit restoreBackupFinished();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
} else {
|
||||
emit errorOccurred(errorCode);
|
||||
@@ -176,7 +178,6 @@ QString SettingsUiController::getAppVersion()
|
||||
void SettingsUiController::clearSettings()
|
||||
{
|
||||
m_settingsController->clearSettings();
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
emit resetLanguageToSystem();
|
||||
|
||||
@@ -205,8 +206,9 @@ bool SettingsUiController::isAutoStartEnabled()
|
||||
void SettingsUiController::toggleAutoStart(bool enable)
|
||||
{
|
||||
m_settingsController->toggleAutoStart(enable);
|
||||
emit autoStartChanged();
|
||||
emit startMinimizedChanged();
|
||||
if (!enable) {
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
}
|
||||
|
||||
bool SettingsUiController::isStartMinimizedEnabled()
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include "core/controllers/settingsController.h"
|
||||
#include "core/controllers/serversController.h"
|
||||
#include "ui/controllers/languageUiController.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "core/utils/errorCodes.h"
|
||||
#include "core/utils/routeModes.h"
|
||||
#include "core/utils/commonStructs.h"
|
||||
@@ -15,6 +17,7 @@ class SettingsUiController : public QObject
|
||||
public:
|
||||
explicit SettingsUiController(SettingsController* settingsController,
|
||||
ServersController* serversController,
|
||||
LanguageUiController* languageUiController,
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_PROPERTY(QString primaryDns READ getPrimaryDns WRITE setPrimaryDns NOTIFY primaryDnsChanged)
|
||||
@@ -29,7 +32,6 @@ public:
|
||||
Q_PROPERTY(bool isDevGatewayEnv READ isDevGatewayEnv WRITE toggleDevGatewayEnv NOTIFY devGatewayEnvChanged)
|
||||
|
||||
Q_PROPERTY(bool isHomeAdLabelVisible READ isHomeAdLabelVisible NOTIFY isHomeAdLabelVisibleChanged)
|
||||
Q_PROPERTY(bool autoStartEnabled READ isAutoStartEnabled NOTIFY autoStartChanged)
|
||||
Q_PROPERTY(bool startMinimized READ isStartMinimizedEnabled NOTIFY startMinimizedChanged)
|
||||
|
||||
public slots:
|
||||
@@ -120,7 +122,7 @@ signals:
|
||||
|
||||
void loggingDisableByWatcher();
|
||||
|
||||
void appLanguageChanged();
|
||||
void appLanguageChanged(const LanguageSettings::AvailableLanguageEnum language);
|
||||
void resetLanguageToSystem();
|
||||
|
||||
void onNotificationStateChanged();
|
||||
@@ -133,12 +135,12 @@ signals:
|
||||
void activityResumed();
|
||||
|
||||
void isHomeAdLabelVisibleChanged(bool visible);
|
||||
void autoStartChanged();
|
||||
void startMinimizedChanged();
|
||||
|
||||
private:
|
||||
SettingsController* m_settingsController;
|
||||
ServersController* m_serversController;
|
||||
LanguageUiController* m_languageUiController;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,7 +30,7 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
|
||||
switch (role) {
|
||||
case SubscriptionStatusRole: {
|
||||
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
|
||||
return QStringLiteral("<p><a style=\"color: #28c840;\">%1</a>").arg(tr("Active"));
|
||||
return tr("Active");
|
||||
}
|
||||
|
||||
return apiUtils::isSubscriptionExpired(m_accountInfoData.subscriptionEndDate)
|
||||
|
||||
@@ -27,7 +27,6 @@ QVariant ClientManagementModel::data(const QModelIndex &index, int role) const
|
||||
auto userData = client.value(configKey::userData).toObject();
|
||||
|
||||
switch (role) {
|
||||
case ClientIdRole: return client.value(configKey::clientId).toString();
|
||||
case ClientNameRole: return userData.value(configKey::clientName).toString();
|
||||
case CreationDateRole: return userData.value(configKey::creationDate).toString();
|
||||
case LatestHandshakeRole: return userData.value(configKey::latestHandshake).toString();
|
||||
@@ -63,7 +62,6 @@ void ClientManagementModel::updateClientName(int row, const QString &newName)
|
||||
QHash<int, QByteArray> ClientManagementModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
roles[ClientIdRole] = "clientId";
|
||||
roles[ClientNameRole] = "clientName";
|
||||
roles[CreationDateRole] = "creationDate";
|
||||
roles[LatestHandshakeRole] = "latestHandshake";
|
||||
|
||||
@@ -10,8 +10,7 @@ class ClientManagementModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
ClientIdRole = Qt::UserRole + 1,
|
||||
ClientNameRole,
|
||||
ClientNameRole = Qt::UserRole + 1,
|
||||
CreationDateRole,
|
||||
LatestHandshakeRole,
|
||||
DataReceivedRole,
|
||||
|
||||
@@ -23,10 +23,6 @@ public:
|
||||
Q_INVOKABLE int containerFromString(const QString &container) const {
|
||||
return static_cast<int>(amnezia::ContainerUtils::containerFromString(container));
|
||||
}
|
||||
|
||||
Q_INVOKABLE bool isUnsupportedContainer(int containerIndex) const {
|
||||
return amnezia::ContainerUtils::isUnsupportedContainer(static_cast<amnezia::DockerContainer>(containerIndex));
|
||||
}
|
||||
};
|
||||
|
||||
#endif // CONTAINERPROPS_H
|
||||
|
||||
@@ -67,7 +67,6 @@ QVariant ContainersModel::data(const QModelIndex &index, int role) const
|
||||
case IsCurrentlyProcessedRole: return container == static_cast<DockerContainer>(m_processedContainerIndex);
|
||||
case IsSupportedRole: return ContainerUtils::isSupportedByCurrentPlatform(container);
|
||||
case IsShareableRole: return ContainerUtils::isShareable(container);
|
||||
case IsUnsupportedContainerRole: return ContainerUtils::isUnsupportedContainer(container);
|
||||
case IsVpnContainerRole: return ContainerUtils::containerService(container) == ServiceType::Vpn;
|
||||
case IsServiceContainerRole: return ContainerUtils::containerService(container) == ServiceType::Other;
|
||||
case IsIpsecRole: return container == DockerContainer::Ipsec;
|
||||
@@ -143,8 +142,7 @@ bool ContainersModel::hasInstalledProtocols()
|
||||
|
||||
bool ContainersModel::isInstallationAllowed(DockerContainer container)
|
||||
{
|
||||
return container != DockerContainer::Awg
|
||||
&& !ContainerUtils::isUnsupportedContainer(container);
|
||||
return container != DockerContainer::Awg;
|
||||
}
|
||||
|
||||
void ContainersModel::openContainerSettings(int containerIndex)
|
||||
@@ -178,7 +176,6 @@ QHash<int, QByteArray> ContainersModel::roleNames() const
|
||||
roles[IsCurrentlyProcessedRole] = "isCurrentlyProcessed";
|
||||
roles[IsSupportedRole] = "isSupported";
|
||||
roles[IsShareableRole] = "isShareable";
|
||||
roles[IsUnsupportedContainerRole] = "isUnsupportedContainer";
|
||||
roles[IsInstallationAllowedRole] = "isInstallationAllowed";
|
||||
roles[InstallPageOrderRole] = "installPageOrder";
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ public:
|
||||
IsSupportedRole,
|
||||
IsShareableRole,
|
||||
|
||||
IsUnsupportedContainerRole,
|
||||
|
||||
InstallPageOrderRole,
|
||||
|
||||
// Container type check roles
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
#include "core/protocols/protocolUtils.h"
|
||||
#include "core/utils/constants/configKeys.h"
|
||||
#include "core/utils/constants/protocolConstants.h"
|
||||
#include "core/utils/networkUtilities.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QRegularExpression>
|
||||
|
||||
using namespace amnezia;
|
||||
using namespace ProtocolUtils;
|
||||
@@ -276,7 +272,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
|
||||
}
|
||||
|
||||
if (!m_protocolConfig.serverConfig.isThirdPartyConfig) {
|
||||
applyDefaultsToServerConfig(m_protocolConfig.serverConfig, false);
|
||||
applyDefaultsToServerConfig(m_protocolConfig.serverConfig);
|
||||
}
|
||||
|
||||
m_originalProtocolConfig = m_protocolConfig;
|
||||
@@ -287,7 +283,7 @@ void XrayConfigModel::updateModel(amnezia::DockerContainer container, const amne
|
||||
}
|
||||
}
|
||||
|
||||
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config, bool fillFlowDefault)
|
||||
void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &config)
|
||||
{
|
||||
if (config.port.isEmpty()) {
|
||||
config.port = protocols::xray::defaultPort;
|
||||
@@ -310,7 +306,7 @@ void XrayConfigModel::applyDefaultsToServerConfig(amnezia::XrayServerConfig &con
|
||||
config.security = protocols::xray::defaultSecurity;
|
||||
}
|
||||
|
||||
if (fillFlowDefault && config.flow.isEmpty()) {
|
||||
if (config.flow.isEmpty()) {
|
||||
config.flow = protocols::xray::defaultFlow;
|
||||
}
|
||||
|
||||
@@ -589,87 +585,3 @@ QString XrayConfigModel::mkcpDefaultWriteBufferSize()
|
||||
{
|
||||
return QString::fromLatin1(protocols::xray::defaultMkcpWriteBufferSize);
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool isValidSingleHost(const QString &t)
|
||||
{
|
||||
if (t.isEmpty() || t.length() > 253) {
|
||||
return false;
|
||||
}
|
||||
QHostAddress a(t);
|
||||
if (a.protocol() == QHostAddress::IPv4Protocol) {
|
||||
return NetworkUtilities::checkIPv4Format(t);
|
||||
}
|
||||
if (a.protocol() == QHostAddress::IPv6Protocol) {
|
||||
return true;
|
||||
}
|
||||
static const QRegularExpression onlyDigits(QStringLiteral(R"(^\d+$)"));
|
||||
if (onlyDigits.match(t).hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
QRegExp re = NetworkUtilities::domainRegExp();
|
||||
re.setCaseSensitivity(Qt::CaseInsensitive);
|
||||
return re.exactMatch(t);
|
||||
}
|
||||
}
|
||||
|
||||
bool XrayConfigModel::isValidHost(const QString &host)
|
||||
{
|
||||
const QString t = host.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return isValidSingleHost(t);
|
||||
}
|
||||
|
||||
bool XrayConfigModel::isValidSni(const QString &sni)
|
||||
{
|
||||
const QString t = sni.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
if (t.startsWith(QLatin1String("*."))) {
|
||||
return isValidSingleHost(t.mid(2));
|
||||
}
|
||||
return isValidSingleHost(t);
|
||||
}
|
||||
|
||||
bool XrayConfigModel::isValidPath(const QString &path)
|
||||
{
|
||||
const QString t = path.trimmed();
|
||||
if (t.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
return t.startsWith(QLatin1Char('/'));
|
||||
}
|
||||
|
||||
QStringList XrayConfigModel::validationErrors() const
|
||||
{
|
||||
QStringList errs;
|
||||
const auto &srv = m_protocolConfig.serverConfig;
|
||||
|
||||
if (!srv.port.isEmpty()) {
|
||||
bool ok = false;
|
||||
const int p = srv.port.toInt(&ok);
|
||||
if (!ok || p < 1 || p > 65535) {
|
||||
errs << tr("Port must be in the range of 1 to 65535");
|
||||
}
|
||||
}
|
||||
|
||||
if (srv.security == QLatin1String("tls") || srv.security == QLatin1String("reality")) {
|
||||
if (!isValidSni(srv.sni)) {
|
||||
errs << tr("SNI: enter a valid IP address or domain name");
|
||||
}
|
||||
}
|
||||
|
||||
if (srv.transport == QLatin1String("xhttp")) {
|
||||
if (!isValidHost(srv.xhttp.host)) {
|
||||
errs << tr("Host: enter a valid IP address or domain name");
|
||||
}
|
||||
if (!isValidPath(srv.xhttp.path)) {
|
||||
errs << tr("Path must start with \"/\"");
|
||||
}
|
||||
}
|
||||
|
||||
return errs;
|
||||
}
|
||||
|
||||
@@ -118,11 +118,6 @@ public:
|
||||
Q_INVOKABLE static QString mkcpDefaultReadBufferSize();
|
||||
Q_INVOKABLE static QString mkcpDefaultWriteBufferSize();
|
||||
|
||||
Q_INVOKABLE static bool isValidHost(const QString &host);
|
||||
Q_INVOKABLE static bool isValidSni(const QString &sni);
|
||||
Q_INVOKABLE static bool isValidPath(const QString &path);
|
||||
Q_INVOKABLE QStringList validationErrors() const;
|
||||
|
||||
public slots:
|
||||
void updateModel(amnezia::DockerContainer container, const amnezia::XrayProtocolConfig& protocolConfig);
|
||||
amnezia::XrayProtocolConfig getProtocolConfig();
|
||||
@@ -142,7 +137,7 @@ private:
|
||||
amnezia::XrayProtocolConfig m_protocolConfig;
|
||||
amnezia::XrayProtocolConfig m_originalProtocolConfig;
|
||||
|
||||
void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config, bool fillFlowDefault = true);
|
||||
void applyDefaultsToServerConfig(amnezia::XrayServerConfig& config);
|
||||
};
|
||||
|
||||
#endif // XRAYCONFIGMODEL_H
|
||||
|
||||
@@ -56,17 +56,14 @@ ListViewType {
|
||||
return
|
||||
}
|
||||
|
||||
var containerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
|
||||
if (!isInstalled) {
|
||||
ServersUiController.processedContainerIndex = containerIndex
|
||||
if (checked) {
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, proxyDefaultServerContainersModel.mapToSource(index))
|
||||
} else {
|
||||
ServersUiController.processedContainerIndex = proxyDefaultServerContainersModel.mapToSource(index)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardProtocolSettings)
|
||||
containersDropDown.closeTriggered()
|
||||
return
|
||||
}
|
||||
|
||||
containersDropDown.closeTriggered()
|
||||
ServersUiController.setDefaultContainer(ServersUiController.defaultServerId, containerIndex)
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
||||
@@ -5,6 +5,7 @@ import QtQuick.Layouts
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
import PageEnum 1.0
|
||||
import ContainerProps 1.0
|
||||
|
||||
import "../Controls2"
|
||||
import "../Controls2/TextTypes"
|
||||
|
||||
@@ -6,36 +6,8 @@ Menu {
|
||||
|
||||
popupType: Popup.Native
|
||||
|
||||
property Item inputBlocker: null
|
||||
|
||||
Component {
|
||||
id: inputBlockerComponent
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
onAboutToShow: {
|
||||
if (!textObj || !textObj.window) {
|
||||
return
|
||||
}
|
||||
|
||||
const contentItem = textObj.window.contentItem
|
||||
if (!inputBlocker) {
|
||||
inputBlocker = inputBlockerComponent.createObject(contentItem)
|
||||
} else {
|
||||
inputBlocker.parent = contentItem
|
||||
}
|
||||
}
|
||||
|
||||
onClosed: {
|
||||
if (inputBlocker) {
|
||||
inputBlocker.destroy()
|
||||
inputBlocker = null
|
||||
}
|
||||
}
|
||||
onAboutToShow: blocker.enabled = true
|
||||
onClosed: blocker.enabled = false
|
||||
|
||||
MenuItem {
|
||||
text: qsTr("C&ut")
|
||||
@@ -59,4 +31,11 @@ Menu {
|
||||
enabled: textObj.length > 0
|
||||
onTriggered: textObj.selectAll()
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: blocker
|
||||
z: 2
|
||||
enabled: false
|
||||
preventStealing: true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,6 @@ Item {
|
||||
property int rootButtonTextBottomMargin: 16
|
||||
|
||||
property real drawerHeight: 0.9
|
||||
property bool fitContent: false
|
||||
property Item drawerParent
|
||||
property Component listView
|
||||
|
||||
@@ -220,20 +219,12 @@ Item {
|
||||
parent: drawerParent
|
||||
|
||||
anchors.fill: parent
|
||||
property real measuredContentHeight: 0
|
||||
expandedHeight: (root.fitContent && measuredContentHeight > 0)
|
||||
? Math.min(measuredContentHeight, drawerParent.height * root.drawerHeight)
|
||||
: drawerParent.height * root.drawerHeight
|
||||
expandedHeight: drawerParent.height * drawerHeight
|
||||
|
||||
expandedStateContent: Item {
|
||||
id: container
|
||||
implicitHeight: menu.expandedHeight
|
||||
|
||||
property real fitHeight: backButton.implicitHeight + titleLabel.implicitHeight
|
||||
+ (listViewLoader.item ? listViewLoader.item.contentHeight : 0) + 48
|
||||
onFitHeightChanged: menu.measuredContentHeight = fitHeight
|
||||
Component.onCompleted: menu.measuredContentHeight = fitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: header
|
||||
|
||||
@@ -247,7 +238,6 @@ Item {
|
||||
}
|
||||
|
||||
Header2Type {
|
||||
id: titleLabel
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.bottomMargin: 16
|
||||
|
||||
@@ -12,8 +12,8 @@ import "../Controls2/TextTypes"
|
||||
// MinMaxRowType {
|
||||
// minValue: "0"
|
||||
// maxValue: "0"
|
||||
// onMinChanged: function(val) { someProperty = val }
|
||||
// onMaxChanged: function(val) { someProperty = val }
|
||||
// onMinChanged: someProperty = val
|
||||
// onMaxChanged: someProperty = val
|
||||
// }
|
||||
Item {
|
||||
id: root
|
||||
@@ -21,128 +21,41 @@ Item {
|
||||
property string minValue: "0"
|
||||
property string maxValue: "0"
|
||||
|
||||
property int minLimit: 0
|
||||
property int maxLimit: 2147483647
|
||||
|
||||
property string hintText: root.minLimit > 0
|
||||
? (root.minLimit + "–" + root.maxLimit)
|
||||
: ("≤ " + root.maxLimit)
|
||||
|
||||
signal minChanged(string val)
|
||||
signal maxChanged(string val)
|
||||
signal edited()
|
||||
|
||||
implicitHeight: col.implicitHeight
|
||||
implicitWidth: col.implicitWidth
|
||||
implicitHeight: row.implicitHeight
|
||||
implicitWidth: row.implicitWidth
|
||||
|
||||
function clampValue(text) {
|
||||
if (text === "")
|
||||
return ""
|
||||
var n = parseInt(text, 10)
|
||||
if (isNaN(n))
|
||||
return ""
|
||||
if (n < root.minLimit)
|
||||
n = root.minLimit
|
||||
if (n > root.maxLimit)
|
||||
n = root.maxLimit
|
||||
return String(n)
|
||||
}
|
||||
|
||||
function capEdit(tf, holder) {
|
||||
if (tf.text !== "" && parseInt(tf.text, 10) > root.maxLimit) {
|
||||
tf.text = holder.lastValid
|
||||
tf.cursorPosition = tf.text.length
|
||||
} else {
|
||||
holder.lastValid = tf.text
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: col
|
||||
RowLayout {
|
||||
id: row
|
||||
anchors.fill: parent
|
||||
spacing: 4
|
||||
spacing: 10
|
||||
|
||||
RowLayout {
|
||||
id: row
|
||||
// Min field
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
spacing: 10
|
||||
|
||||
// Min field
|
||||
TextFieldWithHeaderType {
|
||||
id: minField
|
||||
property string lastValid: ""
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Min")
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onActiveFocusChanged: {
|
||||
if (minField.textField.activeFocus)
|
||||
minField.lastValid = minField.textField.text
|
||||
}
|
||||
textField.onTextEdited: { root.capEdit(minField.textField, minField); root.edited() }
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampValue(minField.textField.text)
|
||||
if (v !== "" && root.maxValue !== "") {
|
||||
var mx = parseInt(root.maxValue, 10)
|
||||
if (!isNaN(mx) && parseInt(v, 10) > mx)
|
||||
root.maxChanged(v)
|
||||
}
|
||||
if (v !== root.minValue)
|
||||
root.minChanged(v)
|
||||
else if (minField.textField.text !== v)
|
||||
minField.textField.text = v
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: minField.textField
|
||||
property: "text"
|
||||
value: root.minValue
|
||||
when: !minField.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
}
|
||||
|
||||
// Max field
|
||||
TextFieldWithHeaderType {
|
||||
id: maxField
|
||||
property string lastValid: ""
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Max")
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onActiveFocusChanged: {
|
||||
if (maxField.textField.activeFocus)
|
||||
maxField.lastValid = maxField.textField.text
|
||||
}
|
||||
textField.onTextEdited: { root.capEdit(maxField.textField, maxField); root.edited() }
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampValue(maxField.textField.text)
|
||||
if (v !== "" && root.minValue !== "") {
|
||||
var mn = parseInt(root.minValue, 10)
|
||||
if (!isNaN(mn) && parseInt(v, 10) < mn)
|
||||
v = String(mn)
|
||||
}
|
||||
if (v !== root.maxValue)
|
||||
root.maxChanged(v)
|
||||
else if (maxField.textField.text !== v)
|
||||
maxField.textField.text = v
|
||||
}
|
||||
|
||||
Binding {
|
||||
target: maxField.textField
|
||||
property: "text"
|
||||
value: root.maxValue
|
||||
when: !maxField.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
headerText: qsTr("Min")
|
||||
textField.text: root.minValue
|
||||
textField.validator: IntValidator { bottom: 0 }
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== root.minValue) {
|
||||
root.minChanged(textField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SmallTextType {
|
||||
visible: root.hintText !== ""
|
||||
text: root.hintText
|
||||
color: AmneziaStyle.color.mutedGray
|
||||
// Max field
|
||||
TextFieldWithHeaderType {
|
||||
Layout.fillWidth: true
|
||||
headerText: qsTr("Max")
|
||||
textField.text: root.maxValue
|
||||
textField.validator: IntValidator { bottom: 0 }
|
||||
textField.onEditingFinished: {
|
||||
if (textField.text !== root.maxValue) {
|
||||
root.maxChanged(textField.text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,8 @@ PageType {
|
||||
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "serverId"
|
||||
value: ServersUiController.processedServerId
|
||||
roleName: "isCurrentlyProcessed"
|
||||
value: true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -440,7 +440,8 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -561,7 +561,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Awg)
|
||||
}
|
||||
|
||||
var noButtonFunction = function() {}
|
||||
|
||||
@@ -434,7 +434,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.OpenVpn)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -128,7 +128,8 @@ PageType {
|
||||
return
|
||||
}
|
||||
|
||||
InstallController.updateClientConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {}
|
||||
showQuestionDrawer(headerText, descriptionText, yesButtonText, noButtonText, yesButtonFunction, noButtonFunction)
|
||||
|
||||
@@ -129,7 +129,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.WireGuard)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) {
|
||||
|
||||
@@ -112,7 +112,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -15,8 +15,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool editDirty: false
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
anchors.top: parent.top
|
||||
@@ -92,7 +90,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: tlsAlpnDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
@@ -136,7 +133,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: tlsFingerprintDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -179,21 +175,14 @@ PageType {
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: sniFieldTls
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("Server Name (SNI)")
|
||||
textField.text: sni
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== sni)
|
||||
textField.onEditingFinished: {
|
||||
var v = textField.text.trim()
|
||||
if (v !== sni) sni = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
sniFieldTls.errorText = XrayConfigModel.isValidSni(v) ? "" : qsTr("Enter a valid IP address or domain name")
|
||||
root.editDirty = false
|
||||
if (textField.text !== sni) sni = textField.text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -206,7 +195,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: realityFingerprintDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
@@ -249,21 +237,14 @@ PageType {
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: sniFieldReality
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("Server Name (SNI)")
|
||||
textField.text: sni
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9.*_-]*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== sni)
|
||||
textField.onEditingFinished: {
|
||||
var v = textField.text.trim()
|
||||
if (v !== sni) sni = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
sniFieldReality.errorText = XrayConfigModel.isValidSni(v) ? "" : qsTr("Enter a valid IP address or domain name")
|
||||
root.editDirty = false
|
||||
if (textField.text !== sni) sni = textField.text
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -284,15 +265,10 @@ PageType {
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
||||
enabled: visible
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
var errs = XrayConfigModel.validationErrors()
|
||||
if (errs.length > 0) {
|
||||
PageController.showErrorMessage(errs.join("\n"))
|
||||
return
|
||||
}
|
||||
var headerText = qsTr("Save settings?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
@@ -303,7 +279,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
@@ -17,10 +17,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
enableTimer: false
|
||||
|
||||
property bool portDirty: false
|
||||
|
||||
function formatTransport(value) {
|
||||
if (value === "raw") return "RAW (TCP)"
|
||||
if (value === "xhttp") return "XHTTP"
|
||||
@@ -43,8 +39,8 @@ PageType {
|
||||
anchors.right: parent.right
|
||||
anchors.topMargin: 20 + PageController.safeAreaTopMargin
|
||||
|
||||
onActiveFocusChanged: {
|
||||
if (backButton.enabled && backButton.activeFocus) {
|
||||
onFocusChanged: {
|
||||
if (this.activeFocus) {
|
||||
listView.positionViewAtBeginning()
|
||||
}
|
||||
}
|
||||
@@ -64,6 +60,8 @@ PageType {
|
||||
delegate: ColumnLayout {
|
||||
width: listView.width
|
||||
|
||||
property alias focusItemId: textFieldWithHeaderType.textField
|
||||
|
||||
spacing: 0
|
||||
|
||||
Text {
|
||||
@@ -109,43 +107,13 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
enabled: listView.enabled
|
||||
headerText: qsTr("Port")
|
||||
subtitleText: qsTr("1–65535")
|
||||
|
||||
Binding {
|
||||
target: textFieldWithHeaderType.textField
|
||||
property: "text"
|
||||
value: port
|
||||
when: !textFieldWithHeaderType.textField.activeFocus
|
||||
restoreMode: Binding.RestoreNone
|
||||
}
|
||||
|
||||
textField.text: port
|
||||
textField.maximumLength: 5
|
||||
textField.validator: RegularExpressionValidator {
|
||||
regularExpression: /^(|\d{1,4}|[1-5]\d{4}|6[0-4]\d{3}|65[0-4]\d{2}|655[0-2]\d|6553[0-5])$/
|
||||
}
|
||||
textField.onActiveFocusChanged: {
|
||||
if (textField.activeFocus && textField.text === "" && port !== "") {
|
||||
textField.text = port
|
||||
}
|
||||
}
|
||||
textField.onTextChanged: {
|
||||
root.portDirty = (textField.text !== port)
|
||||
textField.validator: IntValidator {
|
||||
bottom: 1; top: 65535
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
var v = textFieldWithHeaderType.textField.text
|
||||
if (v !== "") {
|
||||
var n = parseInt(v, 10)
|
||||
if (isNaN(n) || n < 1)
|
||||
n = 1
|
||||
if (n > 65535)
|
||||
n = 65535
|
||||
v = String(n)
|
||||
if (textFieldWithHeaderType.textField.text !== v)
|
||||
textFieldWithHeaderType.textField.text = v
|
||||
}
|
||||
if (v !== port)
|
||||
port = v
|
||||
root.portDirty = false
|
||||
if (textField.text !== port) port = textField.text
|
||||
}
|
||||
checkEmptyText: true
|
||||
}
|
||||
@@ -204,16 +172,12 @@ PageType {
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
visible: listView.enabled
|
||||
&& (XrayConfigModel.hasUnsavedChanges || root.portDirty)
|
||||
enabled: visible && textFieldWithHeaderType.textField.text !== ""
|
||||
&& (XrayConfigModel.hasUnsavedChanges
|
||||
|| textFieldWithHeaderType.textField.text !== port)
|
||||
enabled: visible && textFieldWithHeaderType.errorText === ""
|
||||
text: qsTr("Save")
|
||||
onClicked: function() {
|
||||
forceActiveFocus()
|
||||
var errs = XrayConfigModel.validationErrors()
|
||||
if (errs.length > 0) {
|
||||
PageController.showErrorMessage(errs.join("\n"))
|
||||
return
|
||||
}
|
||||
var headerText = qsTr("Save settings?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
@@ -229,7 +193,7 @@ PageType {
|
||||
}
|
||||
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling);
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function() {
|
||||
if (!GC.isMobile()) saveButton.forceActiveFocus()
|
||||
|
||||
@@ -15,21 +15,6 @@ import "../Components"
|
||||
PageType {
|
||||
id: root
|
||||
|
||||
property bool editDirty: false
|
||||
|
||||
function clampInt(text, lo, hi) {
|
||||
if (text === "")
|
||||
return ""
|
||||
var n = parseInt(text, 10)
|
||||
if (isNaN(n))
|
||||
return ""
|
||||
if (n < lo)
|
||||
n = lo
|
||||
if (n > hi)
|
||||
n = hi
|
||||
return String(n)
|
||||
}
|
||||
|
||||
BackButtonType {
|
||||
id: backButton
|
||||
anchors.top: parent.top
|
||||
@@ -123,16 +108,10 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("TTI")
|
||||
subtitleText: qsTr("Range 10–100, default %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
|
||||
subtitleText: qsTr("Default: %1 ms", "mKCP TTI").arg(XrayConfigModel.mkcpDefaultTti())
|
||||
textField.text: mkcpTti
|
||||
textField.maximumLength: 3
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^(|\d{1,2}|100)$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== mkcpTti)
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 10, 100)
|
||||
if (v !== mkcpTti) mkcpTti = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== mkcpTti) mkcpTti = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,16 +121,10 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("uplinkCapacity")
|
||||
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
|
||||
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP uplink").arg(XrayConfigModel.mkcpDefaultUplinkCapacity())
|
||||
textField.text: mkcpUplinkCapacity
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== mkcpUplinkCapacity)
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||
if (v !== mkcpUplinkCapacity) mkcpUplinkCapacity = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== mkcpUplinkCapacity) mkcpUplinkCapacity = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,16 +134,10 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("downlinkCapacity")
|
||||
subtitleText: qsTr("≥ 0, default %1 MB/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
|
||||
subtitleText: qsTr("Default: %1 Mbit/s", "mKCP downlink").arg(XrayConfigModel.mkcpDefaultDownlinkCapacity())
|
||||
textField.text: mkcpDownlinkCapacity
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== mkcpDownlinkCapacity)
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||
if (v !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== mkcpDownlinkCapacity) mkcpDownlinkCapacity = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,16 +147,10 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("readBufferSize")
|
||||
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
|
||||
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultReadBufferSize())
|
||||
textField.text: mkcpReadBufferSize
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== mkcpReadBufferSize)
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 1, 2147483647)
|
||||
if (v !== mkcpReadBufferSize) mkcpReadBufferSize = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== mkcpReadBufferSize) mkcpReadBufferSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,16 +160,10 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("writeBufferSize")
|
||||
subtitleText: qsTr("≥ 1, default %1 MB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
|
||||
subtitleText: qsTr("Default: %1 MiB").arg(XrayConfigModel.mkcpDefaultWriteBufferSize())
|
||||
textField.text: mkcpWriteBufferSize
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== mkcpWriteBufferSize)
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 1, 2147483647)
|
||||
if (v !== mkcpWriteBufferSize) mkcpWriteBufferSize = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== mkcpWriteBufferSize) mkcpWriteBufferSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +187,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: modeDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 16
|
||||
Layout.leftMargin: 16
|
||||
@@ -285,46 +239,31 @@ PageType {
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: hostField
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("Host")
|
||||
textField.text: xhttpHost
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9._:,-]*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== xhttpHost)
|
||||
textField.onEditingFinished: {
|
||||
var v = textField.text.trim()
|
||||
if (v !== xhttpHost) xhttpHost = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
hostField.errorText = XrayConfigModel.isValidHost(v) ? "" : qsTr("Enter a valid IP address or domain name")
|
||||
root.editDirty = false
|
||||
if (textField.text !== xhttpHost) xhttpHost = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
TextFieldWithHeaderType {
|
||||
id: pathField
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: 16
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("Path")
|
||||
textField.text: xhttpPath
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9\-._~:\/?#\[\]@!$&'()*+,;=%]*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== xhttpPath)
|
||||
textField.onEditingFinished: {
|
||||
var v = textField.text.trim()
|
||||
if (v !== xhttpPath) xhttpPath = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
pathField.errorText = XrayConfigModel.isValidPath(v) ? "" : qsTr("Path must start with \"/\"")
|
||||
root.editDirty = false
|
||||
if (textField.text !== xhttpPath) xhttpPath = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: headersDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -368,7 +307,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: uplinkMethodDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -448,7 +386,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: sessionPlacementDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -492,7 +429,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: sessionKeyDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -536,7 +472,6 @@ PageType {
|
||||
|
||||
DropDownType {
|
||||
id: seqPlacementDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -585,19 +520,13 @@ PageType {
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("SeqKey")
|
||||
textField.text: xhttpSeqKey
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== xhttpSeqKey)
|
||||
textField.onEditingFinished: {
|
||||
var v = textField.text.trim()
|
||||
if (v !== xhttpSeqKey) xhttpSeqKey = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== xhttpSeqKey) xhttpSeqKey = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
DropDownType {
|
||||
id: uplinkDataPlacementDropDown
|
||||
fitContent: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: 8
|
||||
Layout.leftMargin: 16
|
||||
@@ -646,13 +575,8 @@ PageType {
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("UplinkDataKey")
|
||||
textField.text: xhttpUplinkDataKey
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^[A-Za-z0-9_-]*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== xhttpUplinkDataKey)
|
||||
textField.onEditingFinished: {
|
||||
var v = textField.text.trim()
|
||||
if (v !== xhttpUplinkDataKey) xhttpUplinkDataKey = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== xhttpUplinkDataKey) xhttpUplinkDataKey = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,16 +597,12 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("UplinkChunkSize")
|
||||
subtitleText: qsTr("≥ 0 (0 = off)")
|
||||
textField.text: xhttpUplinkChunkSize
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== xhttpUplinkChunkSize)
|
||||
textField.validator: IntValidator {
|
||||
bottom: 0
|
||||
}
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||
if (v !== xhttpUplinkChunkSize) xhttpUplinkChunkSize = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== xhttpUplinkChunkSize) xhttpUplinkChunkSize = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,16 +612,9 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
Layout.topMargin: 8
|
||||
headerText: qsTr("scMaxBufferedPosts")
|
||||
subtitleText: qsTr("≥ 0")
|
||||
textField.text: xhttpScMaxBufferedPosts
|
||||
textField.maximumLength: 10
|
||||
textField.validator: RegularExpressionValidator { regularExpression: /^\d*$/ }
|
||||
textField.onTextEdited: root.editDirty = (textField.text !== xhttpScMaxBufferedPosts)
|
||||
textField.onEditingFinished: {
|
||||
var v = root.clampInt(textField.text, 0, 2147483647)
|
||||
if (v !== xhttpScMaxBufferedPosts) xhttpScMaxBufferedPosts = v
|
||||
else if (textField.text !== v) textField.text = v
|
||||
root.editDirty = false
|
||||
if (textField.text !== xhttpScMaxBufferedPosts) xhttpScMaxBufferedPosts = textField.text
|
||||
}
|
||||
}
|
||||
|
||||
@@ -720,9 +633,8 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
minValue: xhttpScMaxEachPostBytesMin
|
||||
maxValue: xhttpScMaxEachPostBytesMax
|
||||
onMinChanged: function(val) { xhttpScMaxEachPostBytesMin = val; root.editDirty = false }
|
||||
onMaxChanged: function(val) { xhttpScMaxEachPostBytesMax = val; root.editDirty = false }
|
||||
onEdited: root.editDirty = true
|
||||
onMinChanged: xhttpScMaxEachPostBytesMin = val
|
||||
onMaxChanged: xhttpScMaxEachPostBytesMax = val
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
@@ -740,9 +652,8 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
minValue: xhttpScStreamUpServerSecsMin
|
||||
maxValue: xhttpScStreamUpServerSecsMax
|
||||
onMinChanged: function(val) { xhttpScStreamUpServerSecsMin = val; root.editDirty = false }
|
||||
onMaxChanged: function(val) { xhttpScStreamUpServerSecsMax = val; root.editDirty = false }
|
||||
onEdited: root.editDirty = true
|
||||
onMinChanged: xhttpScStreamUpServerSecsMin = val
|
||||
onMaxChanged: xhttpScStreamUpServerSecsMax = val
|
||||
}
|
||||
|
||||
CaptionTextType {
|
||||
@@ -760,9 +671,8 @@ PageType {
|
||||
Layout.rightMargin: 16
|
||||
minValue: xhttpScMinPostsIntervalMsMin
|
||||
maxValue: xhttpScMinPostsIntervalMsMax
|
||||
onMinChanged: function(val) { xhttpScMinPostsIntervalMsMin = val; root.editDirty = false }
|
||||
onMaxChanged: function(val) { xhttpScMinPostsIntervalMsMax = val; root.editDirty = false }
|
||||
onEdited: root.editDirty = true
|
||||
onMinChanged: xhttpScMinPostsIntervalMsMin = val
|
||||
onMaxChanged: xhttpScMinPostsIntervalMsMax = val
|
||||
}
|
||||
|
||||
// ── Padding and multiplexing ──────────────────────────
|
||||
@@ -818,15 +728,10 @@ PageType {
|
||||
anchors.rightMargin: 16
|
||||
anchors.bottomMargin: 16 + PageController.safeAreaBottomMargin
|
||||
|
||||
visible: listView.enabled && (XrayConfigModel.hasUnsavedChanges || root.editDirty)
|
||||
visible: listView.enabled && XrayConfigModel.hasUnsavedChanges
|
||||
enabled: visible
|
||||
text: qsTr("Save")
|
||||
clickedFunc: function () {
|
||||
var errs = XrayConfigModel.validationErrors()
|
||||
if (errs.length > 0) {
|
||||
PageController.showErrorMessage(errs.join("\n"))
|
||||
return
|
||||
}
|
||||
var headerText = qsTr("Save settings?")
|
||||
var descriptionText = qsTr("All users with whom you shared a connection with will no longer be able to connect to it.")
|
||||
var yesButtonText = qsTr("Continue")
|
||||
@@ -837,7 +742,7 @@ PageType {
|
||||
return
|
||||
}
|
||||
PageController.goToPage(PageEnum.PageSetupWizardInstalling)
|
||||
InstallController.updateServerConfig(ServersUiController.processedServerId, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
InstallController.updateContainer(ServersUiController.processedIndex, ServersUiController.processedContainerIndex, ProtocolEnum.Xray)
|
||||
}
|
||||
var noButtonFunction = function () {
|
||||
if (typeof GC !== "undefined" && !GC.isMobile()) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user