Merge branch 'dev' of github-amnezia:amnezia-vpn/amnezia-client into HEAD

This commit is contained in:
vkamn
2026-02-23 15:10:29 +07:00
70 changed files with 946 additions and 786 deletions

View File

@@ -24,7 +24,7 @@ jobs:
- name: Verify git tag - name: Verify git tag
run: | run: |
TAG_NAME=${{ inputs.RELEASE_VERSION }} TAG_NAME=${{ inputs.RELEASE_VERSION }}
CMAKE_TAG=$(grep 'set(AMNEZIAVPN_VERSION' CMakeLists.txt | sed -E 's/.*AMNEZIAVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/') CMAKE_TAG=$(grep 'set(DEFAULTVPN_VERSION' CMakeLists.txt | sed -E 's/.*DEFAULTVPN_VERSION ([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+).*/\1/')
if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then if [[ "$TAG_NAME" == "$CMAKE_TAG" ]]; then
echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)." echo "Git tag ($TAG_NAME) matches CMakeLists.txt version ($CMAKE_TAG)."
else else

3
.gitignore vendored
View File

@@ -140,3 +140,6 @@ ios-ne-build.sh
macos-ne-build.sh macos-ne-build.sh
macos-signed-build.sh macos-signed-build.sh
macos-with-sign-build.sh macos-with-sign-build.sh
DeveloperIdApplicationCertificate.p12
DeveloperIdInstallerCertificate.p12

4
.gitmodules vendored
View File

@@ -14,3 +14,7 @@
[submodule "client/3rd/QSimpleCrypto"] [submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
branch = 6.6

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR) cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT DefaultVPN) set(PROJECT DefaultVPN)
set(DEFAULTVPN_VERSION 1.0.6.2) set(DEFAULTVPN_VERSION 1.0.6.3)
project(${PROJECT} VERSION ${DEFAULTVPN_VERSION} project(${PROJECT} VERSION ${DEFAULTVPN_VERSION}
DESCRIPTION "DefaultVPN" DESCRIPTION "DefaultVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}") set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH}) set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2105) set(APP_ANDROID_VERSION_CODE 2108)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux") set(MZ_PLATFORM_NAME "linux")
@@ -57,13 +57,14 @@ if(WIN32 AND NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
set(CPACK_GENERATOR "WIX") set(CPACK_GENERATOR "WIX")
set(CPACK_WIX_VERSION 4) set(CPACK_WIX_VERSION 4)
set(CPACK_PACKAGE_NAME "AmneziaVPN") set(CPACK_PACKAGE_NAME "DefaultVPN")
set(CPACK_PACKAGE_VENDOR "AmneziaVPN") set(CPACK_PACKAGE_VENDOR "DefaultVPN")
set(CPACK_PACKAGE_VERSION ${AMNEZIAVPN_VERSION}) set(CPACK_PACKAGE_VERSION ${DEFAULTVPN_VERSION})
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "AmneziaVPN client") set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "DefaultVPN client")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "AmneziaVPN") set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_INSTALL_DIRECTORY "DefaultVPN")
set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}") set(CPACK_PACKAGE_DIRECTORY "${CMAKE_BINARY_DIR}")
set(CPACK_PACKAGE_EXECUTABLES "AmneziaVPN" "AmneziaVPN") set(CPACK_PACKAGE_EXECUTABLES "DefaultVPN" "DefaultVPN")
set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}") set(CPACK_WIX_UPGRADE_GUID "{2D55AC62-96D6-4692-8C05-0D85BBF95485}")
set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico") set(CPACK_WIX_PRODUCT_ICON "${CMAKE_SOURCE_DIR}/client/images/app.ico")

1
client/3rd/qtgamepad vendored Submodule

Submodule client/3rd/qtgamepad added at f72b3e0c62

View File

@@ -59,7 +59,6 @@ target_include_directories(${PROJECT} PUBLIC
if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep) qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_interface.rep)
qt_add_repc_replicas(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../ipc/ipc_process_tun2socks.rep)
endif() endif()
qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc) qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
@@ -228,4 +227,13 @@ if(NOT IOS AND NOT ANDROID AND NOT MACOS_NE)
endif() endif()
target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC}) target_sources(${PROJECT} PRIVATE ${SOURCES} ${HEADERS} ${RESOURCES} ${QRC} ${I18NQRC})
qt_finalize_target(${PROJECT})
# Finalize the executable so Qt can gather/deploy QML modules and plugins correctly (Android needs this).
if(COMMAND qt_import_qml_plugins)
qt_import_qml_plugins(${PROJECT})
endif()
if(COMMAND qt_finalize_executable)
qt_finalize_executable(${PROJECT})
else()
qt_finalize_target(${PROJECT})
endif()

View File

@@ -26,6 +26,8 @@ import android.os.ParcelFileDescriptor
import android.os.SystemClock import android.os.SystemClock
import android.provider.OpenableColumns import android.provider.OpenableColumns
import android.provider.Settings import android.provider.Settings
import android.view.InputDevice
import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@@ -88,6 +90,10 @@ class AmneziaActivity : QtActivity() {
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>() private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>() private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
private var isActivityResumed = false
private var hasWindowFocus = false
private val resumeHandler = Handler(Looper.getMainLooper())
private val vpnServiceEventHandler: Handler by lazy(NONE) { private val vpnServiceEventHandler: Handler by lazy(NONE) {
object : Handler(Looper.getMainLooper()) { object : Handler(Looper.getMainLooper()) {
@@ -260,6 +266,10 @@ class AmneziaActivity : QtActivity() {
} }
override fun onStop() { override fun onStop() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity stops
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Stop Amnezia activity") Log.d(TAG, "Stop Amnezia activity")
doUnbindService() doUnbindService()
mainScope.launch { mainScope.launch {
@@ -271,35 +281,91 @@ class AmneziaActivity : QtActivity() {
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
hasWindowFocus = hasFocus
Log.d(TAG, "Window focus changed: hasFocus=$hasFocus") Log.d(TAG, "Window focus changed: hasFocus=$hasFocus")
// Cancel pending operations if window loses focus
if (!hasFocus) {
resumeHandler.removeCallbacksAndMessages(null)
}
} }
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val deviceId = event.deviceId
val keyCode = event.keyCode
val pressed = event.action == KeyEvent.ACTION_DOWN
val source = event.source
if (deviceId < 0 && pressed) {
when (keyCode) {
KeyEvent.KEYCODE_BUTTON_A,
KeyEvent.KEYCODE_BUTTON_B,
KeyEvent.KEYCODE_BUTTON_X,
KeyEvent.KEYCODE_BUTTON_Y,
KeyEvent.KEYCODE_BUTTON_START,
KeyEvent.KEYCODE_BUTTON_SELECT,
KeyEvent.KEYCODE_DPAD_CENTER -> {
nativeGamepadKeyEvent(0, keyCode, true)
nativeGamepadKeyEvent(0, keyCode, false)
return true
}
}
}
// Real gamepad events (deviceId >= 0)
if (deviceId >= 0) {
val isGamepad = (source and InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD
val isJoystick = (source and InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK
val isDpad = (source and InputDevice.SOURCE_DPAD) == InputDevice.SOURCE_DPAD
if (isGamepad || isJoystick || isDpad) {
nativeGamepadKeyEvent(deviceId, keyCode, pressed)
return true
}
}
return super.dispatchKeyEvent(event)
}
private external fun nativeGamepadKeyEvent(deviceId: Int, keyCode: Int, pressed: Boolean)
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
isActivityResumed = false
// Cancel all pending operations when activity pauses
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Pause Amnezia activity") Log.d(TAG, "Pause Amnezia activity")
} }
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
/* if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { isActivityResumed = true
Log.d(TAG, "Resume Amnezia activity")
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
window.decorView.apply { window.decorView.apply {
invalidate() invalidate()
postDelayed({ resumeHandler.postDelayed({
sendTouch(1f, 1f) // Check if activity is still resumed and has focus before executing
if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(1f, 1f)
}
}, 100) }, 100)
postDelayed({ resumeHandler.postDelayed({
sendTouch(2f, 2f) if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
sendTouch(2f, 2f)
}
}, 200) }, 200)
postDelayed({ resumeHandler.postDelayed({
requestLayout() if (isActivityResumed && hasWindowFocus && !isFinishing && !isDestroyed) {
invalidate() requestLayout()
invalidate()
}
}, 250) }, 250)
} }
} */ }
Log.d(TAG, "Resume Amnezia activity")
} }
private fun configureWindowForEdgeToEdge() { private fun configureWindowForEdgeToEdge() {
@@ -362,6 +428,10 @@ class AmneziaActivity : QtActivity() {
} }
override fun onDestroy() { override fun onDestroy() {
isActivityResumed = false
hasWindowFocus = false
// Cancel all pending operations when activity is destroyed
resumeHandler.removeCallbacksAndMessages(null)
Log.d(TAG, "Destroy Amnezia activity") Log.d(TAG, "Destroy Amnezia activity")
unregisterBroadcastReceiver(notificationStateReceiver) unregisterBroadcastReceiver(notificationStateReceiver)
notificationStateReceiver = null notificationStateReceiver = null

View File

@@ -83,6 +83,26 @@ add_compile_definitions(_WINSOCKAPI_)
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
set(BUILD_WITH_QT6 ON) set(BUILD_WITH_QT6 ON)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain) add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtkeychain)
if(ANDROID)
# Use qtgamepad from amnezia-vpn/qtgamepad repository
# Only if Qt6CorePrivate is available (required by qtgamepad)
find_package(Qt6CorePrivate CONFIG QUIET)
if(Qt6CorePrivate_FOUND)
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/qtgamepad)
# Link both the C++ module and QML plugin
if(TARGET GamepadLegacy)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacy)
endif()
if(TARGET GamepadLegacyQuickPrivate)
target_link_libraries(${PROJECT} PRIVATE GamepadLegacyQuickPrivate)
endif()
message(STATUS "Gamepad support enabled for Android")
else()
message(STATUS "Qt6CorePrivate not found. Gamepad support disabled for Android.")
endif()
endif()
set(LIBS ${LIBS} qt6keychain) set(LIBS ${LIBS} qt6keychain)
include_directories( include_directories(

View File

@@ -181,7 +181,6 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
set(HEADERS ${HEADERS} set(HEADERS ${HEADERS}
${CLIENT_ROOT_DIR}/core/ipcclient.h ${CLIENT_ROOT_DIR}/core/ipcclient.h
${CLIENT_ROOT_DIR}/core/privileged_process.h
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.h
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.h
${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h ${CLIENT_ROOT_DIR}/protocols/openvpnovercloakprotocol.h
@@ -194,7 +193,6 @@ if(WIN32 OR (APPLE AND NOT IOS AND NOT MACOS_NE) OR (LINUX AND NOT ANDROID))
set(SOURCES ${SOURCES} set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/ipcclient.cpp ${CLIENT_ROOT_DIR}/core/ipcclient.cpp
${CLIENT_ROOT_DIR}/core/privileged_process.cpp
${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp ${CLIENT_ROOT_DIR}/mozilla/localsocketcontroller.cpp
${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp ${CLIENT_ROOT_DIR}/ui/systemtray_notificationhandler.cpp
${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp ${CLIENT_ROOT_DIR}/protocols/openvpnprotocol.cpp

View File

@@ -337,6 +337,9 @@ QStringList GatewayController::getProxyUrls(const QString &serviceType, const QS
} else { } else {
baseUrls = QString(PROD_S3_ENDPOINT).split(", "); baseUrls = QString(PROD_S3_ENDPOINT).split(", ");
} }
std::random_device randomDevice;
std::mt19937 generator(randomDevice());
std::shuffle(baseUrls.begin(), baseUrls.end(), generator);
QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY; QByteArray key = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;

View File

@@ -7,7 +7,6 @@ IpcClient::IpcClient(QObject *parent) : QObject(parent)
{ {
m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl())); m_node.connectToNode(QUrl("local:" + amnezia::getIpcServiceUrl()));
m_interface.reset(m_node.acquire<IpcInterfaceReplica>()); m_interface.reset(m_node.acquire<IpcInterfaceReplica>());
m_tun2socks.reset(m_node.acquire<IpcProcessTun2SocksReplica>());
} }
IpcClient& IpcClient::Instance() IpcClient& IpcClient::Instance()
@@ -33,68 +32,43 @@ QSharedPointer<IpcInterfaceReplica> IpcClient::Interface()
return rep; return rep;
} }
QSharedPointer<IpcProcessTun2SocksReplica> IpcClient::InterfaceTun2Socks() QSharedPointer<IpcProcessInterfaceReplica> IpcClient::CreatePrivilegedProcess()
{ {
QSharedPointer<IpcProcessTun2SocksReplica> rep = Instance().m_tun2socks; return withInterface([](QSharedPointer<IpcInterfaceReplica> &iface) -> QSharedPointer<IpcProcessInterfaceReplica> {
if (rep.isNull()) { auto createPrivilegedProcess = iface->createPrivilegedProcess();
qCritical() << "IpcClient::InterfaceTun2Socks: Replica is undefined"; if (!createPrivilegedProcess.waitForFinished()) {
return nullptr; qCritical() << "Failed to create privileged process";
} return nullptr;
if (!rep->waitForSource(1000)) {
qCritical() << "IpcClient::InterfaceTun2Socks: Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qWarning() << "IpcClient::InterfaceTun2Socks(): Replica is invalid";
}
return rep;
}
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
{
QSharedPointer<IpcInterfaceReplica> rep = Interface();
if (!rep) {
qCritical() << "IpcClient::createPrivilegedProcess: Replica is invalid";
return nullptr;
}
QRemoteObjectPendingReply<int> pidReply = rep->createPrivilegedProcess();
if (!pidReply.waitForFinished(5000)){
qCritical() << "IpcClient::createPrivilegedProcess: Failed to execute RO createPrivilegedProcess call";
return nullptr;
}
int pid = pidReply.returnValue();
QSharedPointer<ProcessDescriptor> pd(new ProcessDescriptor());
pd->localSocket.reset(new QLocalSocket(pd->replicaNode.data()));
connect(pd->localSocket.data(), &QLocalSocket::connected, pd->replicaNode.data(), [pd]() {
pd->replicaNode->addClientSideConnection(pd->localSocket.data());
IpcProcessInterfaceReplica *repl = pd->replicaNode->acquire<IpcProcessInterfaceReplica>();
// TODO: rework the unsafe cast below
PrivilegedProcess *priv = static_cast<PrivilegedProcess *>(repl);
pd->ipcProcess.reset(priv);
if (!pd->ipcProcess) {
qWarning() << "Acquire PrivilegedProcess failed";
} else {
pd->ipcProcess->waitForSource(1000);
if (!pd->ipcProcess->isReplicaValid()) {
qWarning() << "PrivilegedProcess replica is not connected!";
}
QObject::connect(pd->ipcProcess.data(), &PrivilegedProcess::destroyed, pd->ipcProcess.data(),
[pd]() { pd->replicaNode->deleteLater(); });
} }
});
pd->localSocket->connectToServer(amnezia::getIpcProcessUrl(pid)); const int pid = createPrivilegedProcess.returnValue();
if (!pd->localSocket->waitForConnected()) {
qCritical() << "IpcClient::createPrivilegedProcess: Failed to connect to process' socket"; auto* node = new QRemoteObjectNode();
node->connectToNode(QUrl(QString("local:%1").arg(amnezia::getIpcProcessUrl(pid))));
QSharedPointer<IpcProcessInterfaceReplica> rep(
node->acquire<IpcProcessInterfaceReplica>(),
[node] (IpcProcessInterfaceReplica *ptr) {
delete ptr;
node->deleteLater();
}
);
if (rep.isNull()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to acquire replica";
return nullptr;
}
if (!rep->waitForSource()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Failed to initialize replica";
return nullptr;
}
if (!rep->isReplicaValid()) {
qCritical() << "IpcClient::CreatePrivilegedProcess(): Replica is invalid";
return nullptr;
}
return rep;
},
[]() -> QSharedPointer<IpcProcessInterfaceReplica> {
return nullptr; return nullptr;
} });
auto processReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
return processReplica;
} }

View File

@@ -5,9 +5,7 @@
#include <QObject> #include <QObject>
#include "rep_ipc_interface_replica.h" #include "rep_ipc_interface_replica.h"
#include "rep_ipc_process_tun2socks_replica.h" #include "rep_ipc_process_interface_replica.h"
#include "privileged_process.h"
class IpcClient : public QObject class IpcClient : public QObject
{ {
@@ -18,8 +16,7 @@ public:
static IpcClient& Instance(); static IpcClient& Instance();
static QSharedPointer<IpcInterfaceReplica> Interface(); static QSharedPointer<IpcInterfaceReplica> Interface();
static QSharedPointer<IpcProcessTun2SocksReplica> InterfaceTun2Socks(); static QSharedPointer<IpcProcessInterfaceReplica> CreatePrivilegedProcess();
static QSharedPointer<PrivilegedProcess> CreatePrivilegedProcess();
template <typename Func> template <typename Func>
static auto withInterface(Func func) static auto withInterface(Func func)
@@ -54,18 +51,6 @@ signals:
private: private:
QRemoteObjectNode m_node; QRemoteObjectNode m_node;
QSharedPointer<IpcInterfaceReplica> m_interface; QSharedPointer<IpcInterfaceReplica> m_interface;
QSharedPointer<IpcProcessTun2SocksReplica> m_tun2socks;
struct ProcessDescriptor {
ProcessDescriptor () {
replicaNode = QSharedPointer<QRemoteObjectNode>(new QRemoteObjectNode());
ipcProcess = QSharedPointer<PrivilegedProcess>();
localSocket = QSharedPointer<QLocalSocket>();
}
QSharedPointer<PrivilegedProcess> ipcProcess;
QSharedPointer<QRemoteObjectNode> replicaNode;
QSharedPointer<QLocalSocket> localSocket;
};
}; };
#endif // IPCCLIENT_H #endif // IPCCLIENT_H

View File

@@ -1,27 +0,0 @@
#include "privileged_process.h"
PrivilegedProcess::PrivilegedProcess() :
IpcProcessInterfaceReplica()
{
}
PrivilegedProcess::~PrivilegedProcess()
{
qDebug() << "PrivilegedProcess::~PrivilegedProcess()";
}
void PrivilegedProcess::waitForFinished(int msecs)
{
QSharedPointer<QEventLoop> loop(new QEventLoop);
connect(this, &PrivilegedProcess::finished, this, [this, loop](int exitCode, QProcess::ExitStatus exitStatus) mutable{
loop->quit();
loop.clear();
});
QTimer::singleShot(msecs, this, [this, loop]() mutable {
loop->quit();
loop.clear();
});
loop->exec();
}

View File

@@ -1,24 +0,0 @@
#ifndef PRIVILEGED_PROCESS_H
#define PRIVILEGED_PROCESS_H
#include <QObject>
#include "rep_ipc_process_interface_replica.h"
// This class is dangerous - instance of this class casted from base class,
// so it support only functions
// Do not add any members into it
//
class PrivilegedProcess : public IpcProcessInterfaceReplica
{
Q_OBJECT
public:
PrivilegedProcess();
~PrivilegedProcess() override;
void waitForFinished(int msecs);
};
#endif // PRIVILEGED_PROCESS_H

View File

@@ -270,12 +270,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
&& !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::initPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::responsePacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::underloadPacketMagicHeader).isUndefined()
&& !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined() && !wgConfig.value(amnezia::config_key::transportPacketMagicHeader).isUndefined()) {
&& !wgConfig.value(amnezia::config_key::specialJunk1).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk2).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk3).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk4).isUndefined()
&& !wgConfig.value(amnezia::config_key::specialJunk5).isUndefined()) {
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount)); json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize)); json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize)); json.insert(amnezia::config_key::junkPacketMaxSize, wgConfig.value(amnezia::config_key::junkPacketMaxSize));

View File

@@ -72,9 +72,9 @@ void NetworkWatcher::initialize() {
connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this, connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this,
&NetworkWatcher::unsecuredNetwork); &NetworkWatcher::unsecuredNetwork);
connect(m_impl, &NetworkWatcherImpl::networkChanged, this, connect(m_impl, &NetworkWatcherImpl::networkChanged, this,
&NetworkWatcher::networkChange); &NetworkWatcher::networkChanged);
connect(m_impl, &NetworkWatcherImpl::sleepMode, this, connect(m_impl, &NetworkWatcherImpl::wakeup, this,
&NetworkWatcher::onSleepMode); &NetworkWatcher::wakeup);
m_impl->initialize(); m_impl->initialize();
// Enable sleep/wake monitoring for VPN auto-reconnection // Enable sleep/wake monitoring for VPN auto-reconnection
@@ -97,12 +97,6 @@ void NetworkWatcher::settingsChanged() {
logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active"; logger.debug() << "NetworkWatcher settings changed - keeping sleep monitoring active";
} }
void NetworkWatcher::onSleepMode()
{
logger.debug() << "Resumed from sleep mode";
emit sleepMode();
}
void NetworkWatcher::unsecuredNetwork(const QString& networkName, void NetworkWatcher::unsecuredNetwork(const QString& networkName,
const QString& networkId) { const QString& networkId) {
logger.debug() << "Unsecured network:" << logger.sensitive(networkName) logger.debug() << "Unsecured network:" << logger.sensitive(networkName)

View File

@@ -29,13 +29,11 @@ public:
// false to restore. // false to restore.
void simulateDisconnection(bool simulatedDisconnection); void simulateDisconnection(bool simulatedDisconnection);
void onSleepMode();
QNetworkInformation::Reachability getReachability(); QNetworkInformation::Reachability getReachability();
signals: signals:
void networkChange(); void networkChanged();
void sleepMode(); void wakeup();
private: private:
void settingsChanged(); void settingsChanged();

View File

@@ -41,7 +41,7 @@ signals:
// TODO: Only windows-networkwatcher has this, the other plattforms should // TODO: Only windows-networkwatcher has this, the other plattforms should
// too. // too.
void networkChanged(QString newBSSID); void networkChanged(QString newBSSID);
void sleepMode(); void wakeup();
private: private:

View File

@@ -41,8 +41,8 @@ void LinuxNetworkWatcher::initialize() {
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this, connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
&LinuxNetworkWatcher::unsecuredNetwork); &LinuxNetworkWatcher::unsecuredNetwork);
connect(m_worker, &LinuxNetworkWatcherWorker::sleepMode, this, connect(m_worker, &LinuxNetworkWatcherWorker::wakeup, this,
&NetworkWatcherImpl::sleepMode); &NetworkWatcherImpl::wakeup);
// Let's wait a few seconds to allow the UI to be fully loaded and shown. // Let's wait a few seconds to allow the UI to be fully loaded and shown.
// This is not strictly needed, but it's better for user experience because // This is not strictly needed, but it's better for user experience because

View File

@@ -200,7 +200,7 @@ void LinuxNetworkWatcherWorker::checkDevices() {
void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state) void LinuxNetworkWatcherWorker::NMStateChanged(quint32 state)
{ {
if (state == NM_STATE_ASLEEP) { if (state == NM_STATE_ASLEEP) {
emit sleepMode(); emit wakeup();
} }
logger.debug() << "NMStateChanged " << state; logger.debug() << "NMStateChanged " << state;

View File

@@ -23,7 +23,7 @@ class LinuxNetworkWatcherWorker final : public QObject {
signals: signals:
void unsecuredNetwork(const QString& networkName, const QString& networkId); void unsecuredNetwork(const QString& networkName, const QString& networkId);
void sleepMode(); void wakeup();
public slots: public slots:
void initialize(); void initialize();

View File

@@ -173,10 +173,10 @@ void PowerNotificationsListener::sleepWakeupCallBack(void *refParam, io_service_
case kIOMessageSystemHasPoweredOn: case kIOMessageSystemHasPoweredOn:
/* Announces that the system and its devices have woken up. */ /* Announces that the system and its devices have woken up. */
logger.debug() << "System has powered on - emitting sleepMode signal from dedicated CFRunLoop thread"; logger.debug() << "System has powered on - emitting wakeup signal from dedicated CFRunLoop thread";
if (listener->m_watcher) { if (listener->m_watcher) {
// Use QMetaObject::invokeMethod for thread-safe signal emission // Use QMetaObject::invokeMethod for thread-safe signal emission
QMetaObject::invokeMethod(listener->m_watcher, "sleepMode", Qt::QueuedConnection); QMetaObject::invokeMethod(listener->m_watcher, "wakeup", Qt::QueuedConnection);
} }
break; break;

View File

@@ -62,6 +62,9 @@ void WindowsDaemon::prepareActivation(const InterfaceConfig& config, int inetAda
} }
void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) { void WindowsDaemon::activateSplitTunnel(const InterfaceConfig& config, int vpnAdapterIndex) {
if (m_splitTunnelManager == nullptr)
return;
if (config.m_vpnDisabledApps.length() > 0) { if (config.m_vpnDisabledApps.length() > 0) {
m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex); m_splitTunnelManager->start(m_inetAdapterIndex, vpnAdapterIndex);
m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps); m_splitTunnelManager->excludeApps(config.m_vpnDisabledApps);

View File

@@ -41,7 +41,7 @@ LRESULT WindowsNetworkWatcher::PowerWndProcCallback(HWND hwnd, UINT uMsg, WPARAM
switch (uMsg) { switch (uMsg) {
case WM_POWERBROADCAST: case WM_POWERBROADCAST:
if (wParam == PBT_APMRESUMESUSPEND) { if (wParam == PBT_APMRESUMESUSPEND) {
emit obj->sleepMode(); emit obj->wakeup();
} }
break; break;
default: default:

View File

@@ -232,12 +232,6 @@ ErrorCode OpenVpnProtocol::start()
return ErrorCode::AmneziaServiceConnectionFailed; return ErrorCode::AmneziaServiceConnectionFailed;
} }
m_openVpnProcess->waitForSource(5000);
if (!m_openVpnProcess->isInitialized()) {
qWarning() << "IpcProcess replica is not connected!";
setLastError(ErrorCode::AmneziaServiceConnectionFailed);
return ErrorCode::AmneziaServiceConnectionFailed;
}
m_openVpnProcess->setProgram(PermittedProcess::OpenVPN); m_openVpnProcess->setProgram(PermittedProcess::OpenVPN);
QStringList arguments({ QStringList arguments({
"--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort), "--config", configPath(), "--management", m_managementHost, QString::number(mgmtPort),
@@ -246,13 +240,13 @@ ErrorCode OpenVpnProtocol::start()
m_openVpnProcess->setArguments(arguments); m_openVpnProcess->setArguments(arguments);
qDebug() << arguments.join(" "); qDebug() << arguments.join(" ");
connect(m_openVpnProcess.data(), &PrivilegedProcess::errorOccurred, connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::errorOccurred,
[&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; }); [&](QProcess::ProcessError error) { qDebug() << "PrivilegedProcess errorOccurred" << error; });
connect(m_openVpnProcess.data(), &PrivilegedProcess::stateChanged, connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::stateChanged,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; }); [&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, connect(m_openVpnProcess.data(), &IpcProcessInterfaceReplica::finished, this,
[&]() { setConnectionState(Vpn::ConnectionState::Disconnected); }); [&]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_openVpnProcess->start(); m_openVpnProcess->start();

View File

@@ -53,7 +53,7 @@ private:
void updateRouteGateway(QString line); void updateRouteGateway(QString line);
void updateVpnGateway(const QString &line); void updateVpnGateway(const QString &line);
QSharedPointer<PrivilegedProcess> m_openVpnProcess; QSharedPointer<IpcProcessInterfaceReplica> m_openVpnProcess;
}; };
#endif // OPENVPNPROTOCOL_H #endif // OPENVPNPROTOCOL_H

View File

@@ -233,7 +233,7 @@ namespace amnezia
constexpr char defaultResponsePacketMagicHeader[] = "3288052141"; constexpr char defaultResponsePacketMagicHeader[] = "3288052141";
constexpr char defaultTransportPacketMagicHeader[] = "2528465083"; constexpr char defaultTransportPacketMagicHeader[] = "2528465083";
constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858"; constexpr char defaultUnderloadPacketMagicHeader[] = "1766607858";
constexpr char defaultSpecialJunk1[] = "<b 0x084481800001000300000000077469636b65747306776964676574096b696e6f706f69736b0272750000010001c00c0005000100000039001806776964676574077469636b6574730679616e646578c025c0390005000100000039002b1765787465726e616c2d7469636b6574732d776964676574066166697368610679616e646578036e657400c05d000100010000001c000457fafe25>"; constexpr char defaultSpecialJunk1[] = "<r 2><b 0x858000010001000000000669636c6f756403636f6d0000010001c00c000100010000105a00044d583737>";
constexpr char defaultSpecialJunk2[] = ""; constexpr char defaultSpecialJunk2[] = "";
constexpr char defaultSpecialJunk3[] = ""; constexpr char defaultSpecialJunk3[] = "";
constexpr char defaultSpecialJunk4[] = ""; constexpr char defaultSpecialJunk4[] = "";

View File

@@ -15,7 +15,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
m_impl.reset(new LocalSocketController()); m_impl.reset(new LocalSocketController());
connect(m_impl.get(), &ControllerImpl::connected, this, connect(m_impl.get(), &ControllerImpl::connected, this,
[this](const QString &pubkey, const QDateTime &connectionTimestamp) { [this](const QString &pubkey, const QDateTime &connectionTimestamp) {
emit connectionStateChanged(Vpn::ConnectionState::Connected); setConnectionState(Vpn::ConnectionState::Connected);
}); });
connect(m_impl.get(), &ControllerImpl::statusUpdated, this, connect(m_impl.get(), &ControllerImpl::statusUpdated, this,
[this](const QString& serverIpv4Gateway, [this](const QString& serverIpv4Gateway,
@@ -38,7 +38,7 @@ WireguardProtocol::WireguardProtocol(const QJsonObject &configuration, QObject *
}); });
connect(m_impl.get(), &ControllerImpl::disconnected, this, connect(m_impl.get(), &ControllerImpl::disconnected, this,
[this]() { emit connectionStateChanged(Vpn::ConnectionState::Disconnected); }); [this]() { setConnectionState(Vpn::ConnectionState::Disconnected); });
m_impl->initialize(nullptr, nullptr); m_impl->initialize(nullptr, nullptr);
} }

View File

@@ -1,6 +1,7 @@
#include "xrayprotocol.h" #include "xrayprotocol.h"
#include "core/ipcclient.h" #include "core/ipcclient.h"
#include "ipc.h"
#include "utilities.h" #include "utilities.h"
#include "core/networkUtilities.h" #include "core/networkUtilities.h"
@@ -9,14 +10,37 @@
#include <QJsonObject> #include <QJsonObject>
#include <QNetworkInterface> #include <QNetworkInterface>
#include <QJsonDocument> #include <QJsonDocument>
#include <QtCore/qlogging.h>
#include <QtCore/qobjectdefs.h>
#include <QtCore/qprocess.h>
#ifdef Q_OS_MACOS
static const QString tunName = "utun22";
#else
static const QString tunName = "tun2";
#endif
XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent) XrayProtocol::XrayProtocol(const QJsonObject &configuration, QObject *parent) : VpnProtocol(configuration, parent)
{ {
readXrayConfiguration(configuration);
m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr; m_vpnGateway = amnezia::protocols::xray::defaultLocalAddr;
m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr; m_vpnLocalAddress = amnezia::protocols::xray::defaultLocalAddr;
m_t2sProcess = IpcClient::InterfaceTun2Socks(); m_routeGateway = NetworkUtilities::getGatewayAndIface().first;
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt());
m_remoteAddress = NetworkUtilities::getIPAddress(m_rawConfig.value(amnezia::config_key::hostName).toString());
const QString primaryDns = configuration.value(amnezia::config_key::dns1).toString();
m_dnsServers.push_back(QHostAddress(primaryDns));
if (primaryDns != amnezia::protocols::dns::amneziaDnsIp) {
const QString secondaryDns = configuration.value(amnezia::config_key::dns2).toString();
m_dnsServers.push_back(QHostAddress(secondaryDns));
}
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject();
if (xrayConfiguration.isEmpty()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject();
}
m_xrayConfig = xrayConfiguration;
} }
XrayProtocol::~XrayProtocol() XrayProtocol::~XrayProtocol()
@@ -29,72 +53,16 @@ ErrorCode XrayProtocol::start()
{ {
qDebug() << "XrayProtocol::start()"; qDebug() << "XrayProtocol::start()";
const ErrorCode err = IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) { return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
iface->xrayStart(QJsonDocument(m_xrayConfig).toJson()); auto xrayStart = iface->xrayStart(QJsonDocument(m_xrayConfig).toJson());
return ErrorCode::NoError; if (!xrayStart.waitForFinished() || !xrayStart.returnValue()) {
qCritical() << "Failed to start xray";
return ErrorCode::XrayExecutableCrashed;
}
return startTun2Socks();
}, [] () { }, [] () {
return ErrorCode::AmneziaServiceConnectionFailed; return ErrorCode::AmneziaServiceConnectionFailed;
}); });
if (err != ErrorCode::NoError)
return err;
setConnectionState(Vpn::ConnectionState::Connecting);
return startTun2Sock();
}
ErrorCode XrayProtocol::startTun2Sock()
{
m_t2sProcess->start();
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::stateChanged, this,
[&](QProcess::ProcessState newState) { qDebug() << "PrivilegedProcess stateChanged" << newState; });
connect(m_t2sProcess.data(), &IpcProcessTun2SocksReplica::setConnectionState, this, [&](int vpnState) {
qDebug() << "PrivilegedProcess setConnectionState " << vpnState;
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (vpnState == Vpn::ConnectionState::Connected) {
setConnectionState(Vpn::ConnectionState::Connecting);
QList<QHostAddress> dnsAddr;
dnsAddr.push_back(QHostAddress(m_primaryDNS));
// We don't use secondary DNS if primary DNS is AmneziaDNS
if (!m_primaryDNS.contains(amnezia::protocols::dns::amneziaDnsIp)) {
dnsAddr.push_back(QHostAddress(m_secondaryDNS));
}
#ifdef Q_OS_WIN
QThread::msleep(8000);
#endif
#ifdef Q_OS_MACOS
QThread::msleep(5000);
iface->createTun("utun22", amnezia::protocols::xray::defaultLocalAddr);
iface->updateResolvers("utun22", dnsAddr);
#endif
#ifdef Q_OS_LINUX
QThread::msleep(1000);
iface->createTun("tun2", amnezia::protocols::xray::defaultLocalAddr);
iface->updateResolvers("tun2", dnsAddr);
#endif
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
iface->routeAddList(m_vpnGateway, QStringList() << "1.0.0.0/8" << "2.0.0.0/7" << "4.0.0.0/6" << "8.0.0.0/5" << "16.0.0.0/4" << "32.0.0.0/3" << "64.0.0.0/2" << "128.0.0.0/1");
}
iface->StopRoutingIpv6();
#ifdef Q_OS_WIN
iface->updateResolvers("tun2", dnsAddr);
#endif
setConnectionState(Vpn::ConnectionState::Connected);
}
#if !defined(Q_OS_MACOS)
if (vpnState == Vpn::ConnectionState::Disconnected) {
setConnectionState(Vpn::ConnectionState::Disconnected);
iface->deleteTun("tun2");
iface->StartRoutingIpv6();
iface->clearSavedRoutes();
}
#endif
});
});
return ErrorCode::NoError;
} }
void XrayProtocol::stop() void XrayProtocol::stop()
@@ -102,43 +70,177 @@ void XrayProtocol::stop()
qDebug() << "XrayProtocol::stop()"; qDebug() << "XrayProtocol::stop()";
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) { IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
#ifdef AMNEZIA_DESKTOP auto disableKillSwitch = iface->disableKillSwitch();
QRemoteObjectPendingReply<bool> StartRoutingIpv6Resp = iface->StartRoutingIpv6(); if (!disableKillSwitch.waitForFinished() || !disableKillSwitch.returnValue())
if (!StartRoutingIpv6Resp.waitForFinished(1000)) { qWarning() << "Failed to disable killswitch";
qWarning() << "XrayProtocol::stop(): Failed to start routing ipv6";
}
QRemoteObjectPendingReply<bool> restoreResolvers = iface->restoreResolvers(); auto StartRoutingIpv6 = iface->StartRoutingIpv6();
if (!restoreResolvers.waitForFinished(1000)) { if (!StartRoutingIpv6.waitForFinished() || !StartRoutingIpv6.returnValue())
qWarning() << "XrayProtocol::stop(): Failed to restore resolvers"; qWarning() << "Failed to start routing ipv6";
}
#if !defined(Q_OS_MACOS) auto restoreResolvers = iface->restoreResolvers();
QRemoteObjectPendingReply<bool> deleteTunResp = iface->deleteTun("tun2"); if (!restoreResolvers.waitForFinished() || !restoreResolvers.returnValue())
if (!deleteTunResp.waitForFinished(1000)) { qWarning() << "Failed to restore resolvers";
qWarning() << "XrayProtocol::stop(): Failed to delete tun";
} auto deleteTun = iface->deleteTun(tunName);
#endif if (!deleteTun.waitForFinished() || !deleteTun.returnValue())
#endif qWarning() << "Failed to delete tun";
iface->xrayStop();
auto xrayStop = iface->xrayStop();
if (!xrayStop.waitForFinished() || !xrayStop.returnValue())
qWarning() << "Failed to stop xray";
}); });
if (m_t2sProcess) { if (m_tun2socksProcess) {
m_t2sProcess->stop(); m_tun2socksProcess->blockSignals(true);
QThread::msleep(200);
#ifndef Q_OS_WIN
m_tun2socksProcess->terminate();
auto waitForFinished = m_tun2socksProcess->waitForFinished(1000);
if (!waitForFinished.waitForFinished() || !waitForFinished.returnValue()) {
qWarning() << "Failed to terminate tun2socks. Killing the process...";
m_tun2socksProcess->kill();
}
#else
// terminate does not do anything useful on Windows
// so just kill the process
m_tun2socksProcess->kill();
#endif
m_tun2socksProcess->close();
m_tun2socksProcess.reset();
} }
setConnectionState(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
} }
void XrayProtocol::readXrayConfiguration(const QJsonObject &configuration) ErrorCode XrayProtocol::startTun2Socks()
{ {
QJsonObject xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::Xray)).toObject(); m_tun2socksProcess = IpcClient::CreatePrivilegedProcess();
if (xrayConfiguration.isEmpty()) { if (!m_tun2socksProcess->waitForSource()) {
xrayConfiguration = configuration.value(ProtocolProps::key_proto_config_data(Proto::SSXray)).toObject(); return ErrorCode::AmneziaServiceConnectionFailed;
} }
m_xrayConfig = xrayConfiguration;
m_routeMode = static_cast<Settings::RouteMode>(configuration.value(amnezia::config_key::splitTunnelType).toInt()); m_tun2socksProcess->setProgram(PermittedProcess::Tun2Socks);
m_primaryDNS = configuration.value(amnezia::config_key::dns1).toString(); m_tun2socksProcess->setArguments({"-device", QString("tun://%1").arg(tunName), "-proxy", "socks5://127.0.0.1:10808" });
m_secondaryDNS = configuration.value(amnezia::config_key::dns2).toString();
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, [this]() {
auto readAllStandardOutput = m_tun2socksProcess->readAllStandardOutput();
if (!readAllStandardOutput.waitForFinished()) {
qWarning() << "Failed to read output from tun2socks";
return;
}
const QString line = readAllStandardOutput.returnValue();
if (!line.contains("[TCP]") && !line.contains("[UDP]"))
qDebug() << "[tun2socks]:" << line;
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) {
disconnect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::readyReadStandardOutput, this, nullptr);
if (ErrorCode res = setupRouting(); res != ErrorCode::NoError) {
stop();
setLastError(res);
} else {
setConnectionState(Vpn::ConnectionState::Connected);
}
}
}, Qt::QueuedConnection);
connect(m_tun2socksProcess.data(), &IpcProcessInterfaceReplica::finished, this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
if (exitStatus == QProcess::ExitStatus::CrashExit) {
qCritical() << "Tun2socks process crashed!";
} else {
qCritical() << QString("Tun2socks process was closed with %1 exit code").arg(exitCode);
}
stop();
setLastError(ErrorCode::Tun2SockExecutableCrashed);
}, Qt::QueuedConnection);
m_tun2socksProcess->start();
return ErrorCode::NoError;
}
ErrorCode XrayProtocol::setupRouting() {
return IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> iface) -> ErrorCode {
#ifdef Q_OS_WIN
const int inetAdapterIndex = NetworkUtilities::AdapterIndexTo(QHostAddress(m_remoteAddress));
#endif
auto createTun = iface->createTun(tunName, amnezia::protocols::xray::defaultLocalAddr);
if (!createTun.waitForFinished() || !createTun.returnValue()) {
qCritical() << "Failed to assign IP address for TUN";
return ErrorCode::InternalError;
}
auto updateResolvers = iface->updateResolvers(tunName, m_dnsServers);
if (!updateResolvers.waitForFinished() || !updateResolvers.returnValue()) {
qCritical() << "Failed to set DNS resolvers for TUN";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
int vpnAdapterIndex = -1;
QList<QNetworkInterface> netInterfaces = QNetworkInterface::allInterfaces();
for (auto& netInterface : netInterfaces) {
for (auto& address : netInterface.addressEntries()) {
if (m_vpnLocalAddress == address.ip().toString())
vpnAdapterIndex = netInterface.index();
}
}
#else
static const int vpnAdapterIndex = 0;
#endif
const bool killSwitchEnabled = QVariant(m_rawConfig.value(config_key::killSwitchOption).toString()).toBool();
if (killSwitchEnabled) {
if (vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("vpnServer", m_remoteAddress);
auto enableKillSwitch = IpcClient::Interface()->enableKillSwitch(config, vpnAdapterIndex);
if (!enableKillSwitch.waitForFinished() || !enableKillSwitch.returnValue()) {
qCritical() << "Failed to enable killswitch";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get vpnAdapterIndex. Killswitch disabled";
}
if (m_routeMode == Settings::RouteMode::VpnAllSites) {
static const QStringList subnets = { "1.0.0.0/8", "2.0.0.0/7", "4.0.0.0/6", "8.0.0.0/5", "16.0.0.0/4", "32.0.0.0/3", "64.0.0.0/2", "128.0.0.0/1" };
auto routeAddList = iface->routeAddList(m_vpnGateway, subnets);
if (!routeAddList.waitForFinished() || routeAddList.returnValue() != subnets.count()) {
qCritical() << "Failed to set routes for TUN";
return ErrorCode::InternalError;
}
}
auto StopRoutingIpv6 = iface->StopRoutingIpv6();
if (!StopRoutingIpv6.waitForFinished() || !StopRoutingIpv6.returnValue()) {
qCritical() << "Failed to disable IPv6 routing";
return ErrorCode::InternalError;
}
#ifdef Q_OS_WIN
if (inetAdapterIndex != -1 && vpnAdapterIndex != -1) {
QJsonObject config = m_rawConfig;
config.insert("inetAdapterIndex", inetAdapterIndex);
config.insert("vpnAdapterIndex", vpnAdapterIndex);
config.insert("vpnGateway", m_vpnGateway);
config.insert("vpnServer", m_remoteAddress);
auto enablePeerTraffic = iface->enablePeerTraffic(config);
if (!enablePeerTraffic.waitForFinished() || !enablePeerTraffic.returnValue()) {
qCritical() << "Failed to enable peer traffic";
return ErrorCode::InternalError;
}
} else
qWarning() << "Failed to get adapter indexes. Split-tunneling disabled";
#endif
return ErrorCode::NoError;
},
[] () {
return ErrorCode::AmneziaServiceConnectionFailed;
});
} }

View File

@@ -6,6 +6,7 @@
#include "core/ipcclient.h" #include "core/ipcclient.h"
#include "vpnprotocol.h" #include "vpnprotocol.h"
#include "settings.h" #include "settings.h"
#include <QtCore/qsharedpointer.h>
class XrayProtocol : public VpnProtocol class XrayProtocol : public VpnProtocol
{ {
@@ -14,19 +15,18 @@ public:
virtual ~XrayProtocol() override; virtual ~XrayProtocol() override;
ErrorCode start() override; ErrorCode start() override;
ErrorCode startTun2Sock();
void stop() override; void stop() override;
private: private:
void readXrayConfiguration(const QJsonObject &configuration); ErrorCode setupRouting();
ErrorCode startTun2Socks();
QJsonObject m_xrayConfig; QJsonObject m_xrayConfig;
Settings::RouteMode m_routeMode; Settings::RouteMode m_routeMode;
QString m_primaryDNS; QList<QHostAddress> m_dnsServers;
QString m_secondaryDNS; QString m_remoteAddress;
#ifndef Q_OS_IOS
QSharedPointer<IpcProcessTun2SocksReplica> m_t2sProcess; QSharedPointer<IpcProcessInterfaceReplica> m_tun2socksProcess;
#endif
}; };
#endif // XRAYPROTOCOL_H #endif // XRAYPROTOCOL_H

View File

@@ -131,6 +131,7 @@
<file>ui/qml/Components/AdLabel.qml</file> <file>ui/qml/Components/AdLabel.qml</file>
<file>ui/qml/Components/ConnectButton.qml</file> <file>ui/qml/Components/ConnectButton.qml</file>
<file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file> <file>ui/qml/Components/ConnectionTypeSelectionDrawer.qml</file>
<file>ui/qml/Components/GamepadLoader.qml</file>
<file>ui/qml/Components/HomeContainersListView.qml</file> <file>ui/qml/Components/HomeContainersListView.qml</file>
<file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file> <file>ui/qml/Components/HomeSplitTunnelingDrawer.qml</file>
<file>ui/qml/Components/InstalledAppsDrawer.qml</file> <file>ui/qml/Components/InstalledAppsDrawer.qml</file>

View File

@@ -387,6 +387,51 @@ bool ApiConfigsController::fillAvailableServices()
} }
QJsonObject data = QJsonDocument::fromJson(responseBody).object(); QJsonObject data = QJsonDocument::fromJson(responseBody).object();
#if defined(Q_OS_IOS) || defined(MACOS_NE)
QEventLoop waitProducts;
bool productsFetched = false;
QString productPrice;
QString productCurrency;
IosController::Instance()->fetchProducts(QStringList() << QStringLiteral("amnezia_premium_6_month"),
[&](const QList<QVariantMap> &products,
const QStringList &invalidIds,
const QString &errorString) {
if (!errorString.isEmpty() || products.isEmpty()) {
qWarning().noquote() << "[IAP] Failed to fetch product price:" << errorString;
} else {
const auto &product = products.first();
productPrice = product.value("price").toString();
productCurrency = product.value("currencyCode").toString();
productsFetched = true;
qInfo().noquote() << "[IAP] Fetched product price:" << productPrice << productCurrency;
}
waitProducts.quit();
});
waitProducts.exec();
if (productsFetched && !productPrice.isEmpty()) {
QJsonArray services = data.value("services").toArray();
for (int i = 0; i < services.size(); ++i) {
QJsonObject service = services[i].toObject();
if (service.value(configKey::serviceType).toString() == serviceType::amneziaPremium) {
QJsonObject serviceInfo = service.value(configKey::serviceInfo).toObject();
QString formattedPrice = productPrice;
if (!productCurrency.isEmpty()) {
formattedPrice += " " + productCurrency;
}
serviceInfo["price"] = formattedPrice;
service[configKey::serviceInfo] = serviceInfo;
services[i] = service;
data["services"] = services;
qInfo().noquote() << "[IAP] Updated premium service price in data:" << formattedPrice;
break;
}
}
}
#endif
m_apiServicesModel->updateModel(data); m_apiServicesModel->updateModel(data);
if (m_apiServicesModel->rowCount() > 0) { if (m_apiServicesModel->rowCount() > 0) {
m_apiServicesModel->setServiceIndex(0); m_apiServicesModel->setServiceIndex(0);

View File

@@ -326,7 +326,7 @@ void ExportController::generateXrayConfig(const QString &clientName)
vlessServer.spiderX = realitySettings.value("spiderX").toString(""); vlessServer.spiderX = realitySettings.value("spiderX").toString("");
} }
m_nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "AmneziaVPN"); m_nativeConfigString = amnezia::serialization::vless::Serialize(vlessServer, "DefaultVPN");
emit exportConfigChanged(); emit exportConfigChanged();
} }

View File

@@ -291,6 +291,8 @@ void ImportController::processNativeWireGuardConfig()
clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0"; clientProtocolConfig[config_key::cookieReplyPacketJunkSize] = "0";
clientProtocolConfig[config_key::transportPacketJunkSize] = "0"; clientProtocolConfig[config_key::transportPacketJunkSize] = "0";
clientProtocolConfig[config_key::specialJunk1] = protocols::awg::defaultSpecialJunk1;
clientProtocolConfig[config_key::isObfuscationEnabled] = true; clientProtocolConfig[config_key::isObfuscationEnabled] = true;
serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson()); serverProtocolConfig[config_key::last_config] = QString(QJsonDocument(clientProtocolConfig).toJson());

View File

@@ -112,7 +112,11 @@ QVariant ApiServicesModel::data(const QModelIndex &index, int role) const
if (price == "free") { if (price == "free") {
return tr("Free"); return tr("Free");
} }
#if defined(Q_OS_IOS) || defined(MACOS_NE)
return tr("%1 $").arg(price);
#else
return tr("%1 $/month").arg(price); return tr("%1 $/month").arg(price);
#endif
} }
case EndDateRole: { case EndDateRole: {
return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy"); return QDateTime::fromString(apiServiceData.subscription.endDate, Qt::ISODate).toLocalTime().toString("d MMM yyyy");

View File

@@ -0,0 +1,38 @@
import QtQuick
import QtGamepadLegacy
Item {
id: root
property alias gamepad: gamepad
property alias gamepadKeyNav: gamepadKeyNav
Gamepad {
id: gamepad
deviceId: GamepadManager.connectedGamepads.length > 0 ? GamepadManager.connectedGamepads[0] : -1
onButtonStartChanged: {
if (buttonStart) {
ServersModel.setProcessedServerIndex(ServersModel.defaultIndex)
ConnectionController.connectButtonClicked()
}
}
}
GamepadKeyNavigation {
id: gamepadKeyNav
gamepad: gamepad
active: true
}
Connections {
target: GamepadManager
function onConnectedGamepadsChanged() {
if (GamepadManager.connectedGamepads.length > 0) {
gamepad.deviceId = GamepadManager.connectedGamepads[0]
} else {
gamepad.deviceId = -1
}
}
}
}

View File

@@ -111,11 +111,11 @@ Button {
color: { color: {
if (root.enabled) { if (root.enabled) {
if (root.pressed) { if (root.pressed) {
return pressedColor return root.pressedColor
} }
return root.hovered ? hoveredColor : defaultColor return root.hovered ? root.hoveredColor : root.defaultColor
} else { } else {
return disabledColor return root.disabledColor
} }
} }

View File

@@ -49,10 +49,29 @@ Item {
return drawerContent.state === stateName return drawerContent.state === stateName
} }
function isDrawerType2(obj) {
return obj && typeof obj.drawerExpandedStateName !== "undefined" &&
typeof obj.drawerCollapsedStateName !== "undefined"
}
function isDescendantOfDrawer(obj) {
var current = obj
while (current && current !== root.parent) {
if (isDrawerType2(current)) {
return true
}
current = current.parent
}
return false
}
function findComponent(obj, typeCtor) { function findComponent(obj, typeCtor) {
if (!obj) if (!obj)
return null return null
if (isDrawerType2(obj) || isDescendantOfDrawer(obj))
return null
if (obj instanceof typeCtor) if (obj instanceof typeCtor)
return obj return obj

View File

@@ -19,9 +19,6 @@ Item {
property string buttonText property string buttonText
property string buttonImageSource property string buttonImageSource
property string buttonImageColor: AmneziaStyle.color.midnightBlack
property string buttonBackgroundColor: AmneziaStyle.color.paleGray
property string buttonHoveredColor: AmneziaStyle.color.lightGray
property var clickedFunc property var clickedFunc
property alias textField: textField property alias textField: textField
@@ -70,7 +67,7 @@ Item {
border.width: 1 border.width: 1
Behavior on border.color { Behavior on border.color {
PropertyAnimation { duration: 100 } PropertyAnimation { duration: 200 }
} }
RowLayout { RowLayout {
@@ -124,7 +121,7 @@ Item {
background: Rectangle { background: Rectangle {
anchors.fill: parent anchors.fill: parent
color: root.enabled ? root.backgroundColor : root.backgroundDisabledColor color: root.backgroundDisabledColor
} }
onTextChanged: { onTextChanged: {
@@ -189,14 +186,6 @@ Item {
focusPolicy: Qt.NoFocus focusPolicy: Qt.NoFocus
text: root.buttonText text: root.buttonText
leftImageSource: root.buttonImageSource leftImageSource: root.buttonImageSource
leftImageColor: root.buttonImageColor
defaultColor: root.buttonBackgroundColor
hoveredColor: root.buttonHoveredColor
pressedColor: root.buttonHoveredColor
disabledColor: AmneziaStyle.color.transparent
borderWidth: 0
anchors.top: content.top anchors.top: content.top
anchors.bottom: content.bottom anchors.bottom: content.bottom
@@ -204,7 +193,7 @@ Item {
height: content.implicitHeight height: content.implicitHeight
width: content.implicitHeight width: content.implicitHeight
squareLeftSide: false squareLeftSide: true
clickedFunc: function() { clickedFunc: function() {
if (root.clickedFunc && typeof root.clickedFunc === "function") { if (root.clickedFunc && typeof root.clickedFunc === "function") {

View File

@@ -396,9 +396,7 @@ PageType {
PageController.showNotificationMessage(qsTr("Cannot remove server during active connection")) PageController.showNotificationMessage(qsTr("Cannot remove server during active connection"))
} else { } else {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
if (ApiConfigsController.deactivateDevice(true)) { InstallController.removeProcessedServer()
InstallController.removeProcessedServer()
}
PageController.showBusyIndicator(false) PageController.showBusyIndicator(false)
} }
} }

View File

@@ -224,7 +224,6 @@ PageType {
height: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin height: addAppButton.implicitHeight + 48 + SettingsController.safeAreaBottomMargin
color: AmneziaStyle.color.midnightBlack color: AmneziaStyle.color.midnightBlack
opacity: 0.8
RowLayout { RowLayout {
id: addAppButton id: addAppButton

View File

@@ -240,7 +240,6 @@ PageType {
height: addSiteButton.implicitHeight + 48 height: addSiteButton.implicitHeight + 48
color: AmneziaStyle.color.midnightBlack color: AmneziaStyle.color.midnightBlack
opacity: 0.8
RowLayout { RowLayout {
id: addSiteButton id: addSiteButton

View File

@@ -97,16 +97,32 @@ PageType {
} }
} }
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter
textFormat: Text.PlainText
color: AmneziaStyle.color.mutedGray
font.pixelSize: 12
text: qsTr("Charged to your Apple ID at confirmation. Renews automatically unless auto-renew is turned off at least 24 hours before period end. Manage in Apple ID settings.")
}
BasicButtonType { BasicButtonType {
id: continueButton id: continueButton
Layout.fillWidth: true Layout.fillWidth: true
Layout.topMargin: 32 Layout.topMargin: 32
Layout.bottomMargin: 32 Layout.bottomMargin: 16
Layout.leftMargin: 16 Layout.leftMargin: 16
Layout.rightMargin: 16 Layout.rightMargin: 16
text: qsTr("Connect") text: ApiServicesModel.getSelectedServiceType() === "amnezia-premium" ? qsTr("Subscribe Now") : qsTr("Connect")
clickedFunc: function() { clickedFunc: function() {
PageController.showBusyIndicator(true) PageController.showBusyIndicator(true)
@@ -121,6 +137,37 @@ PageType {
} }
} }
} }
ParagraphTextType {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
Layout.bottomMargin: 32
visible: (Qt.platform.os === "ios" || IsMacOsNeBuild) && ApiServicesModel.getSelectedServiceType() === "amnezia-premium"
horizontalAlignment: Text.AlignHCenter
textFormat: Text.RichText
color: AmneziaStyle.color.mutedGray
font.pixelSize: 12
text: {
var termsUrl = "https://www.apple.com/legal/internet-services/itunes/dev/stdeula/"
var privacyUrl = LanguageModel.getCurrentSiteUrl("policy")
return qsTr("By continuing, you agree to the <a href=\"%1\" style=\"color: #FBB26A;\">Terms of Use</a> and <a href=\"%2\" style=\"color: #FBB26A;\">Privacy Policy</a>").arg(termsUrl).arg(privacyUrl)
}
onLinkActivated: function(link) {
Qt.openUrlExternally(link)
}
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.NoButton
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
}
}
} }
} }

View File

@@ -83,6 +83,11 @@ Window {
} }
} }
Loader {
active: Qt.platform.os === "android"
source: Qt.platform.os === "android" ? "Components/GamepadLoader.qml" : ""
}
Connections { Connections {
objectName: "pageControllerConnections" objectName: "pageControllerConnections"

View File

@@ -41,7 +41,6 @@ VpnConnection::VpnConnection(std::shared_ptr<Settings> settings, QObject *parent
m_checkTimer.setInterval(1000); m_checkTimer.setInterval(1000);
connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged); connect(IosController::Instance(), &IosController::connectionStateChanged, this, &VpnConnection::onConnectionStateChanged);
connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged); connect(IosController::Instance(), &IosController::bytesChanged, this, &VpnConnection::onBytesChanged);
#endif #endif
} }
@@ -59,7 +58,7 @@ void VpnConnection::onKillSwitchModeChanged(bool enabled)
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){ IpcClient::withInterface([enabled](QSharedPointer<IpcInterfaceReplica> iface){
QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled); QRemoteObjectPendingReply<bool> reply = iface->refreshKillSwitch(enabled);
if (reply.waitForFinished(1000) && reply.returnValue()) if (reply.waitForFinished() && reply.returnValue())
qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed"; qDebug() << "VpnConnection::onKillSwitchModeChanged: Killswitch refreshed";
else else
qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call"; qWarning() << "VpnConnection::onKillSwitchModeChanged: Failed to execute remote refreshKillSwitch call";
@@ -73,60 +72,57 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
auto container = m_settings->defaultContainer(m_settings->defaultServerIndex()); auto container = m_settings->defaultContainer(m_settings->defaultServerIndex());
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) { IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (state == Vpn::ConnectionState::Connected) { switch (state) {
iface->resetIpStack(); case Vpn::ConnectionState::Connected: {
iface->flushDns(); iface->resetIpStack();
if (!ContainerProps::isAwgContainer(container) && auto flushDns = iface->flushDns();
container != DockerContainer::WireGuard) { if (flushDns.waitForFinished() && flushDns.returnValue())
QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString(); qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString(); else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
if (m_settings->isSitesSplitTunnelingEnabled()) { if (!ContainerProps::isAwgContainer(container) &&
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0"); container != DockerContainer::WireGuard) {
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size(); QString dns1 = m_vpnConfiguration.value(config_key::dns1).toString();
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) { QString dns2 = m_vpnConfiguration.value(config_key::dns2).toString();
QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress()); // TODO: add error code handling for all routeAddList (or rework the code below)
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode()); iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << dns1 << dns2);
if (m_settings->isSitesSplitTunnelingEnabled()) {
iface->routeDeleteList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0");
// qDebug() << "VpnConnection::onConnectionStateChanged :: adding custom routes, count:" << forwardIps.size();
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
QTimer::singleShot(1000, m_vpnProtocol.data(),
[this]() { addSitesRoutes(m_vpnProtocol->vpnGateway(), m_settings->routeMode()); });
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "0.0.0.0/1");
iface->routeAddList(m_vpnProtocol->vpnGateway(), QStringList() << "128.0.0.0/1");
iface->routeAddList(m_vpnProtocol->routeGateway(), QStringList() << remoteAddress());
addSitesRoutes(m_vpnProtocol->routeGateway(), m_settings->routeMode());
}
} }
} }
} } break;
case Vpn::ConnectionState::Disconnected:
case Vpn::ConnectionState::Error: {
auto flushDns = iface->flushDns();
if (flushDns.waitForFinished() && flushDns.returnValue())
qDebug() << "VpnConnection::onConnectionStateChanged: Successfully flushed DNS";
else
qWarning() << "VpnConnection::onConnectionStateChanged: Failed to flush DNS";
if (container != DockerContainer::Ipsec) { auto clearSavedRoutes = iface->clearSavedRoutes();
if (startNetworkCheckIfReady()) { if (clearSavedRoutes.waitForFinished() && clearSavedRoutes.returnValue())
m_pendingNetworkCheck = false; qDebug() << "VpnConnection::onConnectionStateChanged: Successfully cleared saved routes";
} else { else
m_pendingNetworkCheck = true; qWarning() << "VpnConnection::onConnectionStateChanged: Failed to clear saved routes";
qWarning() << "Deferring startNetworkCheck; missing gateway/local address" } break;
<< m_vpnProtocol->vpnGateway() << m_vpnProtocol->vpnLocalAddress(); default:
} break;
} else {
m_pendingNetworkCheck = false;
}
} else if (state == Vpn::ConnectionState::Error) {
m_pendingNetworkCheck = false;
iface->flushDns();
if (m_settings->isSitesSplitTunnelingEnabled()) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
iface->clearSavedRoutes();
}
}
} else if (state == Vpn::ConnectionState::Connecting) {
} else if (state == Vpn::ConnectionState::Disconnected) {
m_pendingNetworkCheck = false;
auto result = iface->stopNetworkCheck();
result.waitForFinished(3000);
} }
}); });
#endif #endif
@@ -140,7 +136,6 @@ void VpnConnection::onConnectionStateChanged(Vpn::ConnectionState state)
m_checkTimer.stop(); m_checkTimer.stop();
} }
#endif #endif
emit connectionStateChanged(state);
} }
const QString &VpnConnection::remoteAddress() const const QString &VpnConnection::remoteAddress() const
@@ -185,7 +180,11 @@ void VpnConnection::addSitesRoutes(const QString &gw, Settings::RouteMode mode)
}); });
m_settings->addVpnSite(mode, site, ip); m_settings->addVpnSite(mode, site, ip);
} }
flushDns(); IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (reply.waitForFinished() || !reply.returnValue())
qWarning() << "VpnConnection::addSitesRoutes: Failed to flush DNS";
});
break; break;
} }
} }
@@ -200,48 +199,6 @@ QSharedPointer<VpnProtocol> VpnConnection::vpnProtocol() const
return m_vpnProtocol; return m_vpnProtocol;
} }
void VpnConnection::addRoutes(const QStringList &ips)
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (connectionState() == Vpn::ConnectionState::Connected) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
iface->routeAddList(m_vpnProtocol->vpnGateway(), ips);
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeAddList(m_vpnProtocol->routeGateway(), ips);
}
}
});
#endif
}
void VpnConnection::deleteRoutes(const QStringList &ips)
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
if (connectionState() == Vpn::ConnectionState::Connected) {
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
iface->routeDeleteList(vpnProtocol()->vpnGateway(), ips);
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
iface->routeDeleteList(m_vpnProtocol->routeGateway(), ips);
}
}
});
#endif
}
void VpnConnection::flushDns()
{
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
auto reply = iface->flushDns();
if (reply.waitForFinished(1000) || !reply.returnValue()) {
qWarning() << "VpnConnection::flushDns(): Failed to flush DNS";
}
});
#endif
}
void VpnConnection::disconnectSlots() void VpnConnection::disconnectSlots()
{ {
if (m_vpnProtocol) { if (m_vpnProtocol) {
@@ -265,19 +222,15 @@ ErrorCode VpnConnection::lastError() const
void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container,
const QJsonObject &vpnConfiguration) const QJsonObject &vpnConfiguration)
{ {
qDebug() << QString("ConnectToVpn, Server index is %1, container is %2, route mode is") qDebug() << QString("Trying to connect to VPN, server index is %1, container is %2, route mode is")
.arg(serverIndex) .arg(serverIndex)
.arg(ContainerProps::containerToString(container)) .arg(ContainerProps::containerToString(container))
<< m_settings->routeMode(); << m_settings->routeMode();
m_remoteAddress = NetworkUtilities::getIPAddress(credentials.hostName); m_remoteAddress = NetworkUtilities::getIPAddress(credentials.hostName);
emit connectionStateChanged(Vpn::ConnectionState::Connecting); setConnectionState(Vpn::ConnectionState::Connecting);
m_pendingNetworkCheck = false;
m_vpnConfiguration = vpnConfiguration; m_vpnConfiguration = vpnConfiguration;
m_serverIndex = serverIndex;
m_serverCredentials = credentials;
m_dockerContainer = container;
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
if (m_vpnProtocol) { if (m_vpnProtocol) {
@@ -293,7 +246,7 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE) #if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) && !defined(MACOS_NE)
m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration)); m_vpnProtocol.reset(VpnProtocol::factory(container, m_vpnConfiguration));
if (!m_vpnProtocol) { if (!m_vpnProtocol) {
emit connectionStateChanged(Vpn::ConnectionState::Error); setConnectionState(Vpn::ConnectionState::Error);
return; return;
} }
m_vpnProtocol->prepare(); m_vpnProtocol->prepare();
@@ -311,75 +264,23 @@ void VpnConnection::connectToVpn(int serverIndex, const ServerCredentials &crede
createProtocolConnections(); createProtocolConnections();
ErrorCode errorCode = m_vpnProtocol->start(); if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
if (errorCode != ErrorCode::NoError) setConnectionState(Vpn::ConnectionState::Error);
emit connectionStateChanged(Vpn::ConnectionState::Error); emit vpnProtocolError(err);
}
void VpnConnection::restartConnection()
{
// Only reconnect if VPN was connected before sleep/network change
if (!m_wasConnectedBeforeSleep) {
qDebug() << "VPN was not connected before sleep/network change, skipping reconnection";
return;
} }
qDebug() << "VPN was connected before sleep/network change, attempting reconnection";
this->disconnectFromVpn();
#ifdef Q_OS_LINUX
QThread::msleep(5000);
#endif
this->connectToVpn(m_serverIndex, m_serverCredentials, m_dockerContainer, m_vpnConfiguration);
// Reset the flag after reconnection attempt
m_wasConnectedBeforeSleep = false;
} }
void VpnConnection::createProtocolConnections() void VpnConnection::createProtocolConnections()
{ {
connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError); connect(m_vpnProtocol.data(), &VpnProtocol::protocolError, this, &VpnConnection::vpnProtocolError);
connect(m_vpnProtocol.data(), SIGNAL(connectionStateChanged(Vpn::ConnectionState)), this, connect(m_vpnProtocol.data(), &VpnProtocol::connectionStateChanged, this, &VpnConnection::setConnectionState);
SLOT(onConnectionStateChanged(Vpn::ConnectionState)));
connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64))); connect(m_vpnProtocol.data(), SIGNAL(bytesChanged(quint64, quint64)), this, SLOT(onBytesChanged(quint64, quint64)));
#ifdef AMNEZIA_DESKTOP #ifdef AMNEZIA_DESKTOP
if (m_connectionLoseHandle) IpcClient::withInterface([this](QSharedPointer<IpcInterfaceReplica> rep) {
disconnect(m_connectionLoseHandle); connect(rep.data(), &IpcInterfaceReplica::networkChanged, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
if (m_networkChangeHandle) connect(rep.data(), &IpcInterfaceReplica::wakeup, this, &VpnConnection::reconnectToVpn, Qt::QueuedConnection);
disconnect(m_networkChangeHandle); });
m_connectionLoseHandle = QMetaObject::Connection();
m_networkChangeHandle = QMetaObject::Connection();
// TODO: replace unsafe IpcClient::Interface() calls
m_connectionLoseHandle = connect(IpcClient::Interface().data(), &IpcInterfaceReplica::connectionLose,
this, [this]() {
qDebug() << "Connection Lose";
auto result = IpcClient::Interface()->stopNetworkCheck();
result.waitForFinished(3000);
// Track VPN state before connection loss
m_wasConnectedBeforeSleep = isConnected();
qDebug() << "VPN was connected before connection loss:" << m_wasConnectedBeforeSleep;
this->restartConnection();
});
m_networkChangeHandle = connect(IpcClient::Interface().data(), &IpcInterfaceReplica::networkChange,
this, [this]() {
qDebug() << "Network change";
// Track VPN state before network change (including sleep/wake)
m_wasConnectedBeforeSleep = isConnected();
qDebug() << "VPN was connected before network change:" << m_wasConnectedBeforeSleep;
this->restartConnection();
});
connect(m_vpnProtocol.data(), &VpnProtocol::tunnelAddressesUpdated,
this, [this](const QString& gateway, const QString& localAddress) {
Q_UNUSED(gateway)
Q_UNUSED(localAddress)
if (connectionState() != Vpn::ConnectionState::Connected) {
return;
}
if (startNetworkCheckIfReady()) {
m_pendingNetworkCheck = false;
}
});
#endif #endif
} }
@@ -482,28 +383,13 @@ void VpnConnection::appendSplitTunnelingConfig()
m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode); m_vpnConfiguration.insert(config_key::appSplitTunnelType, appsRouteMode);
m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray); m_vpnConfiguration.insert(config_key::splitTunnelApps, appsJsonArray);
}
bool VpnConnection::startNetworkCheckIfReady() qDebug() << QString("Site split tunneling is %1, route mode is %2")
{ .arg(m_settings->isSitesSplitTunnelingEnabled() ? "enabled" : "disabled")
#ifdef AMNEZIA_DESKTOP .arg(routeMode);
if (!m_vpnProtocol || m_dockerContainer == DockerContainer::Ipsec) { qDebug() << QString("App split tunneling is %1, route mode is %2")
return false; .arg(m_settings->isAppsSplitTunnelingEnabled() ? "enabled" : "disabled")
} .arg(appsRouteMode);
const QString gateway = m_vpnProtocol->vpnGateway();
const QString localAddress = m_vpnProtocol->vpnLocalAddress();
if (gateway.isEmpty() || localAddress.isEmpty()) {
return false;
}
return IpcClient::withInterface([&](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> reply = iface->startNetworkCheck(gateway, localAddress);
return reply.waitForFinished(1000) && reply.returnValue();
});
#else
return false;
#endif
} }
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
@@ -537,6 +423,27 @@ QString VpnConnection::bytesPerSecToText(quint64 bytes)
return QString("%1 %2").arg(QString::number(mbps, 'f', 2)).arg(tr("Mbps")); // Mbit/s return QString("%1 %2").arg(QString::number(mbps, 'f', 2)).arg(tr("Mbps")); // Mbit/s
} }
void VpnConnection::reconnectToVpn() {
if (m_vpnProtocol.isNull())
return;
if (m_connectionState != Vpn::ConnectionState::Connected) {
qWarning() << QString("Reconnect triggered on %1 during inappropriate state: %2; ignoring slot")
.arg(QMetaEnum::fromType<Vpn::ConnectionState>().valueToKey(m_connectionState));
return;
}
qDebug() << "Reconnect triggered. Reconnecting to the server";
setConnectionState(Vpn::ConnectionState::Reconnecting);
m_vpnProtocol->stop();
if (ErrorCode err = m_vpnProtocol->start(); err != ErrorCode::NoError) {
setConnectionState(Vpn::ConnectionState::Error);
emit vpnProtocolError(err);
}
}
void VpnConnection::disconnectFromVpn() void VpnConnection::disconnectFromVpn()
{ {
#if defined(Q_OS_IOS) || defined(MACOS_NE) #if defined(Q_OS_IOS) || defined(MACOS_NE)
@@ -546,41 +453,26 @@ void VpnConnection::disconnectFromVpn()
#endif #endif
if (m_vpnProtocol.isNull()) { if (m_vpnProtocol.isNull()) {
emit connectionStateChanged(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
return; return;
} }
m_vpnProtocol->stop(); setConnectionState(Vpn::ConnectionState::Disconnecting);
#ifdef AMNEZIA_DESKTOP
IpcClient::withInterface([](QSharedPointer<IpcInterfaceReplica> iface) {
QRemoteObjectPendingReply<bool> flushReply = iface->flushDns();
if (flushReply.waitForFinished(5000) && flushReply.returnValue())
qDebug() << "VpnConnection::disconnectFromVpn(): Successfully flushed DNS";
else
qWarning() << "VpnConnection::disconnectFromVpn(): Failed to flush DNS";
QRemoteObjectPendingReply<bool> clearSavedRoutesReply = iface->clearSavedRoutes();
if (clearSavedRoutesReply.waitForFinished(5000) && clearSavedRoutesReply.returnValue())
qDebug() << "VpnConnection::disconnectFromVpn(): Successfully cleared saved routes";
else
qWarning() << "VpnConnection::disconnectFromVpn(): Failed to clear saved routes";
});
#endif
#ifdef Q_OS_ANDROID #ifdef Q_OS_ANDROID
auto *const connection = new QMetaObject::Connection; auto *const connection = new QMetaObject::Connection;
*connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this, *connection = connect(AndroidController::instance(), &AndroidController::vpnStateChanged, this,
[this, connection](AndroidController::ConnectionState state) { [this, connection](AndroidController::ConnectionState state) {
if (state == AndroidController::ConnectionState::DISCONNECTED) { if (state == AndroidController::ConnectionState::DISCONNECTED) {
onConnectionStateChanged(Vpn::ConnectionState::Disconnected); setConnectionState(Vpn::ConnectionState::Disconnected);
disconnect(*connection); disconnect(*connection);
delete connection; delete connection;
} }
}); });
m_vpnProtocol->stop();
#endif #endif
m_vpnProtocol->stop();
#if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP) #if !defined(Q_OS_ANDROID) && !defined(AMNEZIA_DESKTOP)
m_vpnProtocol->deleteLater(); m_vpnProtocol->deleteLater();
#endif #endif
@@ -588,27 +480,12 @@ void VpnConnection::disconnectFromVpn()
m_vpnProtocol = nullptr; m_vpnProtocol = nullptr;
} }
Vpn::ConnectionState VpnConnection::connectionState() void VpnConnection::setConnectionState(Vpn::ConnectionState state) {
{ onConnectionStateChanged(state);
if (!m_vpnProtocol)
return Vpn::ConnectionState::Disconnected; if (state == Vpn::Disconnected && m_connectionState == Vpn::Reconnecting)
return m_vpnProtocol->connectionState(); return;
}
m_connectionState = state;
bool VpnConnection::isConnected() const emit connectionStateChanged(state);
{
if (m_vpnProtocol.isNull()) {
return false;
}
return m_vpnProtocol->isConnected();
}
bool VpnConnection::isDisconnected() const
{
if (m_vpnProtocol.isNull()) {
return true;
}
return m_vpnProtocol->isDisconnected();
} }

View File

@@ -34,10 +34,6 @@ public:
ErrorCode lastError() const; ErrorCode lastError() const;
bool isConnected() const;
bool isDisconnected() const;
Vpn::ConnectionState connectionState();
QSharedPointer<VpnProtocol> vpnProtocol() const; QSharedPointer<VpnProtocol> vpnProtocol() const;
const QString &remoteAddress() const; const QString &remoteAddress() const;
@@ -48,15 +44,10 @@ public:
#endif #endif
public slots: public slots:
void connectToVpn(int serverIndex, void connectToVpn(int serverIndex, const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration);
const ServerCredentials &credentials, DockerContainer container, const QJsonObject &vpnConfiguration); void reconnectToVpn();
void disconnectFromVpn(); void disconnectFromVpn();
void restartConnection();
void addRoutes(const QStringList &ips);
void deleteRoutes(const QStringList &ips);
void flushDns();
void onKillSwitchModeChanged(bool enabled); void onKillSwitchModeChanged(bool enabled);
void disconnectSlots(); void disconnectSlots();
@@ -71,10 +62,10 @@ protected slots:
void onBytesChanged(quint64 receivedBytes, quint64 sentBytes); void onBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void onConnectionStateChanged(Vpn::ConnectionState state); void onConnectionStateChanged(Vpn::ConnectionState state);
void setConnectionState(Vpn::ConnectionState state);
protected: protected:
QSharedPointer<VpnProtocol> m_vpnProtocol; QSharedPointer<VpnProtocol> m_vpnProtocol;
QMetaObject::Connection m_connectionLoseHandle;
QMetaObject::Connection m_networkChangeHandle;
private: private:
std::shared_ptr<Settings> m_settings; std::shared_ptr<Settings> m_settings;
@@ -82,14 +73,6 @@ private:
QJsonObject m_routeMode; QJsonObject m_routeMode;
QString m_remoteAddress; QString m_remoteAddress;
ServerCredentials m_serverCredentials;
int m_serverIndex;
DockerContainer m_dockerContainer;
// Track VPN state before sleep for smart reconnection
bool m_wasConnectedBeforeSleep = false;
bool m_pendingNetworkCheck = false;
// Only for iOS for now, check counters // Only for iOS for now, check counters
QTimer m_checkTimer; QTimer m_checkTimer;
@@ -100,11 +83,12 @@ private:
void createAndroidConnections(); void createAndroidConnections();
#endif #endif
Vpn::ConnectionState m_connectionState;
void createProtocolConnections(); void createProtocolConnections();
void appendSplitTunnelingConfig(); void appendSplitTunnelingConfig();
void appendKillSwitchConfig(); void appendKillSwitchConfig();
bool startNetworkCheckIfReady();
}; };
#endif // VPNCONNECTION_H #endif // VPNCONNECTION_H

View File

@@ -31,6 +31,7 @@ set SCRIPT_DIR=%PROJECT_DIR:"=%\deploy
set WORK_DIR=%SCRIPT_DIR:"=%\build_%BUILD_ARCH:"=% set WORK_DIR=%SCRIPT_DIR:"=%\build_%BUILD_ARCH:"=%
set APP_NAME=DefaultVPN set APP_NAME=DefaultVPN
set APP_FILENAME=%APP_NAME:"=%.exe set APP_FILENAME=%APP_NAME:"=%.exe
set SERVICE_FILENAME=%APP_NAME:"=%-service.exe
set APP_DOMAIN=org.defaultvpn.package set APP_DOMAIN=org.defaultvpn.package
set OUT_APP_DIR=%WORK_DIR:"=%\client\release set OUT_APP_DIR=%WORK_DIR:"=%\client\release
set PREBILT_DEPLOY_DATA_DIR=%PROJECT_DIR:"=%\client\3rd-prebuilt\deploy-prebuilt\windows\x%BUILD_ARCH:"=% set PREBILT_DEPLOY_DATA_DIR=%PROJECT_DIR:"=%\client\3rd-prebuilt\deploy-prebuilt\windows\x%BUILD_ARCH:"=%
@@ -43,6 +44,7 @@ set STAGE_DIR=%WORK_DIR:"=%\stage
echo "Environment:" echo "Environment:"
echo "WORK_DIR: %WORK_DIR%" echo "WORK_DIR: %WORK_DIR%"
echo "APP_FILENAME: %APP_FILENAME%" echo "APP_FILENAME: %APP_FILENAME%"
echo "SERVICE_FILENAME: %SERVICE_FILENAME%"
echo "PROJECT_DIR: %PROJECT_DIR%" echo "PROJECT_DIR: %PROJECT_DIR%"
echo "SCRIPT_DIR: %SCRIPT_DIR%" echo "SCRIPT_DIR: %SCRIPT_DIR%"
echo "OUT_APP_DIR: %OUT_APP_DIR%" echo "OUT_APP_DIR: %OUT_APP_DIR%"
@@ -74,7 +76,7 @@ if %errorlevel% neq 0 exit /b %errorlevel%
echo "Deploying..." echo "Deploying..."
mkdir "%OUT_APP_DIR%" mkdir "%OUT_APP_DIR%"
copy "%WORK_DIR%\service\server\release\%APP_NAME%-service.exe" "%OUT_APP_DIR%" copy "%WORK_DIR%\service\server\release\%SERVICE_FILENAME%" "%OUT_APP_DIR%"
rem copy "%WORK_DIR%\client\%APP_FILENAME%" "%OUT_APP_DIR%" rem copy "%WORK_DIR%\client\%APP_FILENAME%" "%OUT_APP_DIR%"
copy /Y "%PROJECT_DIR%\client\images\app.ico" "%OUT_APP_DIR%\AmneziaVPN.ico" >nul copy /Y "%PROJECT_DIR%\client\images\app.ico" "%OUT_APP_DIR%\AmneziaVPN.ico" >nul
@@ -83,7 +85,8 @@ echo "Signing exe"
cd %OUT_APP_DIR% cd %OUT_APP_DIR%
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.exe
"%QT_BIN_DIR:"=%\windeployqt" --release --qmldir "%PROJECT_DIR:"=%\client" --force --no-translations "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%" "%QT_BIN_DIR:"=%\windeployqt" --release --qmldir "%PROJECT_DIR:"=%\client" --force --no-translations --force-openssl "%OUT_APP_DIR:"=%\%APP_FILENAME:"=%"
"%QT_BIN_DIR:"=%\windeployqt" --release "%OUT_APP_DIR:"=%\%SERVICE_FILENAME:"=%"
signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll signtool sign /v /n "Privacy Technologies OU" /fd sha256 /tr http://timestamp.comodoca.com/?td=sha256 /td sha256 *.dll

View File

@@ -14,6 +14,8 @@ sc stop DefaultVPN-service
sc delete DefaultVPN-service sc delete DefaultVPN-service
sc stop AmneziaWGTunnel$DefaultVPN sc stop AmneziaWGTunnel$DefaultVPN
sc delete AmneziaWGTunnel$DefaultVPN sc delete AmneziaWGTunnel$DefaultVPN
sc stop DefaultVPNSplitTunnel
sc delete DefaultVPNSplitTunnel
taskkill /IM "DefaultVPN-service.exe" /F taskkill /IM "DefaultVPN-service.exe" /F
taskkill /IM "DefaultVPN.exe" /F taskkill /IM "DefaultVPN.exe" /F

View File

@@ -14,6 +14,8 @@ sc stop DefaultVPN-service
sc delete DefaultVPN-service sc delete DefaultVPN-service
sc stop AmneziaWGTunnel$DefaultVPN sc stop AmneziaWGTunnel$DefaultVPN
sc delete AmneziaWGTunnel$DefaultVPN sc delete AmneziaWGTunnel$DefaultVPN
sc stop DefaultVPNSplitTunnel
sc delete DefaultVPNSplitTunnel
taskkill /IM "DefaultVPN-service.exe" /F taskkill /IM "DefaultVPN-service.exe" /F
taskkill /IM "DefaultVPN.exe" /F taskkill /IM "DefaultVPN.exe" /F

View File

@@ -1,9 +1,9 @@
#!/bin/sh #!/bin/bash
set -e set -e
VERSION=$1 VERSION=$1
if [[ $VERSION = '' ]]; then if [[ -z "$VERSION" ]]; then
echo '::error::VERSION does not set. Exiting with error...' echo '::error::VERSION does not set. Exiting with error...'
exit 1 exit 1
fi fi
@@ -14,20 +14,39 @@ cd dist
echo $VERSION >> VERSION echo $VERSION >> VERSION
curl -s https://api.github.com/repos/amnezia-vpn/amnezia-client/releases/tags/$VERSION | jq -r .body | tr -d '\r' > CHANGELOG curl -s https://api.github.com/repos/amnezia-vpn/amnezia-client/releases/tags/$VERSION | jq -r .body | tr -d '\r' > CHANGELOG
curl -s https://api.github.com/repos/amnezia-vpn/amnezia-client/releases/tags/$VERSION | jq -r .published_at > RELEASE_DATE
if [[ $(cat CHANGELOG) = null ]]; then if [[ $(cat CHANGELOG) = null ]]; then
echo '::error::Release does not exists. Exiting with error...' echo '::error::Release does not exists. Exiting with error...'
exit 1 exit 1
fi fi
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_arm64-v8a.apk # Download files with error handling
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_armeabi-v7a.apk download_file() {
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_x86.apk local url=$1
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_x86_64.apk local filename=$(basename "$url")
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_linux_x64.tar echo "Downloading $filename..."
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_macos.pkg if ! wget -q "$url"; then
wget -q https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_x64.exe echo "::error::Failed to download $filename from $url"
exit 8
fi
echo "Successfully downloaded $filename"
}
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_arm64-v8a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_armeabi-v7a.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_x86.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_android9+_x86_64.apk
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_linux_x64.tar
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_macos.pkg
download_file https://github.com/amnezia-vpn/amnezia-client/releases/download/${VERSION}/DefaultVPN_${VERSION}_x64.exe
cd ../ cd ../
rclone sync ./dist/ r2:/updates/ echo "Syncing to R2..."
if ! rclone sync ./dist/ r2:/updates/; then
echo "::error::Failed to sync files to R2"
exit 8
fi
echo "Deployment completed successfully!"

View File

@@ -11,6 +11,7 @@
namespace amnezia { namespace amnezia {
enum PermittedProcess { enum PermittedProcess {
Invalid,
OpenVPN, OpenVPN,
Wireguard, Wireguard,
Tun2Socks, Tun2Socks,
@@ -19,16 +20,18 @@ enum PermittedProcess {
inline QString permittedProcessPath(PermittedProcess pid) inline QString permittedProcessPath(PermittedProcess pid)
{ {
if (pid == PermittedProcess::OpenVPN) { switch (pid) {
return Utils::openVpnExecPath(); case PermittedProcess::OpenVPN:
} else if (pid == PermittedProcess::Wireguard) { return Utils::openVpnExecPath();
return Utils::wireguardExecPath(); case PermittedProcess::Wireguard:
} else if (pid == PermittedProcess::CertUtil) { return Utils::wireguardExecPath();
return Utils::certUtilPath(); case PermittedProcess::CertUtil:
} else if (pid == PermittedProcess::Tun2Socks) { return Utils::certUtilPath();
return Utils::tun2socksPath(); case PermittedProcess::Tun2Socks:
return Utils::tun2socksPath();
default:
return "";
} }
return "";
} }
@@ -48,6 +51,51 @@ inline QString getIpcProcessUrl(int pid) {
#endif #endif
} }
inline QStringList sanitizeArguments(PermittedProcess proc, const QStringList &args) {
using Validator = std::function<bool(const QString&)>;
QMap<QString, Validator> namedArgs;
QList<Validator> positionalArgs;
switch (proc) {
case Tun2Socks:
namedArgs["-device"] = [](const QString& v) { return v.startsWith("tun://"); };
namedArgs["-proxy"] = [](const QString& v) { return v.startsWith("socks5://"); };
break;
default:
//FIXME
return args;
}
QStringList sanitized;
for (int i = 0, pos = 0; i < args.size(); i++) {
const auto& key = args[i];
if (const auto found = namedArgs.find(key); found != namedArgs.end()) {
const auto validator = found.value();
if (validator) {
if (i + 1 < args.size()) {
const auto& value = args[i+1];
if (validator(value)) {
sanitized << key << value;
i++;
}
}
} else {
sanitized << key;
}
} else if (pos < positionalArgs.size()) {
if (const auto validator = positionalArgs[pos]; validator && validator(key)) {
sanitized << key;
pos++;
}
}
}
return sanitized;
}
} // namespace amnezia } // namespace amnezia

View File

@@ -38,12 +38,13 @@ class IpcInterface
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) ); SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) );
SLOT( bool restoreResolvers() ); SLOT( bool restoreResolvers() );
SLOT(void xrayStart(const QString &config)); SLOT(bool xrayStart(const QString &config));
SLOT(void xrayStop()); SLOT(bool xrayStop());
SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) ); SLOT( bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) );
SLOT( bool stopNetworkCheck() ); SLOT( bool stopNetworkCheck() );
SIGNAL( connectionLose() ); SIGNAL( connectionLose() );
SIGNAL( networkChange() ); SIGNAL( wakeup() );
SIGNAL( networkChanged() );
}; };

View File

@@ -4,6 +4,8 @@
class IpcProcessInterface class IpcProcessInterface
{ {
SLOT( start() ); SLOT( start() );
SLOT( terminate() );
SLOT( kill() );
SLOT( close() ); SLOT( close() );
SLOT( setArguments(const QStringList &arguments) ); SLOT( setArguments(const QStringList &arguments) );
@@ -17,6 +19,11 @@ class IpcProcessInterface
SLOT( QByteArray readAllStandardError() ); SLOT( QByteArray readAllStandardError() );
SLOT( QByteArray readAllStandardOutput() ); SLOT( QByteArray readAllStandardOutput() );
SLOT( bool waitForFinished() );
SLOT( bool waitForFinished(int msecs) );
SLOT( bool waitForStarted() );
SLOT( bool waitForStarted(int msecs) );
SIGNAL( errorOccurred(QProcess::ProcessError error) ); SIGNAL( errorOccurred(QProcess::ProcessError error) );
SIGNAL( finished(int exitCode, QProcess::ExitStatus exitStatus) ); SIGNAL( finished(int exitCode, QProcess::ExitStatus exitStatus) );

View File

@@ -1,11 +0,0 @@
#include <QtCore>
#include <QString>
class IpcProcessTun2Socks
{
SLOT( start() );
SLOT( stop() );
SIGNAL( setConnectionState(int state) );
SIGNAL( stateChanged(QProcess::ProcessState newState) );
};

View File

@@ -304,7 +304,7 @@ bool IpcServer::refreshKillSwitch(bool enabled)
return KillSwitch::instance()->refresh(enabled); return KillSwitch::instance()->refresh(enabled);
} }
void IpcServer::xrayStart(const QString& cfg) bool IpcServer::xrayStart(const QString& cfg)
{ {
#ifdef MZ_DEBUG #ifdef MZ_DEBUG
qDebug() << "IpcServer::xrayStart"; qDebug() << "IpcServer::xrayStart";
@@ -313,7 +313,7 @@ void IpcServer::xrayStart(const QString& cfg)
return Xray::getInstance().startXray(cfg); return Xray::getInstance().startXray(cfg);
} }
void IpcServer::xrayStop() bool IpcServer::xrayStop()
{ {
#ifdef MZ_DEBUG #ifdef MZ_DEBUG
qDebug() << "IpcServer::xrayStop"; qDebug() << "IpcServer::xrayStop";

View File

@@ -10,10 +10,8 @@
#include "ipc.h" #include "ipc.h"
#include "ipcserverprocess.h" #include "ipcserverprocess.h"
#include "ipctun2socksprocess.h"
#include "rep_ipc_interface_source.h" #include "rep_ipc_interface_source.h"
#include "rep_ipc_process_tun2socks_source.h"
class IpcServer : public IpcInterfaceSource class IpcServer : public IpcInterfaceSource
{ {
@@ -44,8 +42,8 @@ public:
virtual bool refreshKillSwitch( bool enabled ) override; virtual bool refreshKillSwitch( bool enabled ) override;
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override; virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override;
virtual bool restoreResolvers() override; virtual bool restoreResolvers() override;
virtual void xrayStart(const QString& cfg) override; virtual bool xrayStart(const QString& cfg) override;
virtual void xrayStop() override; virtual bool xrayStop() override;
virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override; virtual bool startNetworkCheck(const QString& serverIpv4Gateway, const QString& deviceIpv4Address) override;
virtual bool stopNetworkCheck() override; virtual bool stopNetworkCheck() override;
@@ -56,12 +54,10 @@ private:
ProcessDescriptor (QObject *parent = nullptr) { ProcessDescriptor (QObject *parent = nullptr) {
serverNode = QSharedPointer<QRemoteObjectHost>(new QRemoteObjectHost(parent)); serverNode = QSharedPointer<QRemoteObjectHost>(new QRemoteObjectHost(parent));
ipcProcess = QSharedPointer<IpcServerProcess>(new IpcServerProcess(parent)); ipcProcess = QSharedPointer<IpcServerProcess>(new IpcServerProcess(parent));
tun2socksProcess = QSharedPointer<IpcProcessTun2Socks>(new IpcProcessTun2Socks(parent));
localServer = QSharedPointer<QLocalServer>(new QLocalServer(parent)); localServer = QSharedPointer<QLocalServer>(new QLocalServer(parent));
} }
QSharedPointer<IpcServerProcess> ipcProcess; QSharedPointer<IpcServerProcess> ipcProcess;
QSharedPointer<IpcProcessTun2Socks> tun2socksProcess;
QSharedPointer<QRemoteObjectHost> serverNode; QSharedPointer<QRemoteObjectHost> serverNode;
QSharedPointer<QLocalServer> localServer; QSharedPointer<QLocalServer> localServer;
}; };

View File

@@ -40,6 +40,14 @@ void IpcServerProcess::start()
m_process->waitForStarted(); m_process->waitForStarted();
} }
void IpcServerProcess::terminate() {
m_process->terminate();
}
void IpcServerProcess::kill() {
m_process->kill();
}
void IpcServerProcess::close() void IpcServerProcess::close()
{ {
m_process->close(); m_process->close();
@@ -47,7 +55,7 @@ void IpcServerProcess::close()
void IpcServerProcess::setArguments(const QStringList &arguments) void IpcServerProcess::setArguments(const QStringList &arguments)
{ {
m_process->setArguments(arguments); m_process->setArguments(amnezia::sanitizeArguments(m_program, arguments));
} }
void IpcServerProcess::setInputChannelMode(QProcess::InputChannelMode mode) void IpcServerProcess::setInputChannelMode(QProcess::InputChannelMode mode)
@@ -69,7 +77,9 @@ void IpcServerProcess::setProcessChannelMode(QProcess::ProcessChannelMode mode)
void IpcServerProcess::setProgram(int programId) void IpcServerProcess::setProgram(int programId)
{ {
m_process->setProgram(amnezia::permittedProcessPath(static_cast<amnezia::PermittedProcess>(programId))); m_program = static_cast<amnezia::PermittedProcess>(programId);
m_process->setProgram(amnezia::permittedProcessPath(m_program));
m_process->setArguments({});
} }
void IpcServerProcess::setWorkingDirectory(const QString &dir) void IpcServerProcess::setWorkingDirectory(const QString &dir)
@@ -92,4 +102,20 @@ QByteArray IpcServerProcess::readAllStandardOutput()
return m_process->readAllStandardOutput(); return m_process->readAllStandardOutput();
} }
bool IpcServerProcess::waitForStarted() {
return m_process->waitForStarted();
}
bool IpcServerProcess::waitForStarted(int msecs) {
return m_process->waitForStarted(msecs);
}
bool IpcServerProcess::waitForFinished() {
return m_process->waitForFinished();
}
bool IpcServerProcess::waitForFinished(int msecs) {
return m_process->waitForFinished(msecs);
}
#endif #endif

View File

@@ -1,6 +1,7 @@
#ifndef IPCSERVERPROCESS_H #ifndef IPCSERVERPROCESS_H
#define IPCSERVERPROCESS_H #define IPCSERVERPROCESS_H
#include "ipc.h"
#include <QObject> #include <QObject>
#ifndef Q_OS_IOS #ifndef Q_OS_IOS
@@ -14,6 +15,8 @@ public:
virtual ~IpcServerProcess(); virtual ~IpcServerProcess();
void start() override; void start() override;
void terminate() override;
void kill() override;
void close() override; void close() override;
void setArguments(const QStringList &arguments) override; void setArguments(const QStringList &arguments) override;
@@ -27,9 +30,15 @@ public:
QByteArray readAllStandardError() override; QByteArray readAllStandardError() override;
QByteArray readAllStandardOutput() override; QByteArray readAllStandardOutput() override;
bool waitForStarted() override;
bool waitForStarted(int msecs) override;
bool waitForFinished() override;
bool waitForFinished(int msecs) override;
signals: signals:
private: private:
amnezia::PermittedProcess m_program = amnezia::PermittedProcess::Invalid;
QSharedPointer<QProcess> m_process; QSharedPointer<QProcess> m_process;
}; };

View File

@@ -1,79 +0,0 @@
#include "ipctun2socksprocess.h"
#include "ipc.h"
#include <QProcess>
#include <QString>
#include "../protocols/protocols_defs.h"
#ifndef Q_OS_IOS
IpcProcessTun2Socks::IpcProcessTun2Socks(QObject *parent) :
IpcProcessTun2SocksSource(parent),
m_t2sProcess(QSharedPointer<QProcess>(new QProcess()))
{
qDebug() << "IpcProcessTun2Socks::IpcProcessTun2Socks()";
}
IpcProcessTun2Socks::~IpcProcessTun2Socks()
{
qDebug() << "IpcProcessTun2Socks::~IpcProcessTun2Socks()";
}
void IpcProcessTun2Socks::start()
{
connect(m_t2sProcess.data(), &QProcess::stateChanged, this, &IpcProcessTun2Socks::stateChanged);
qDebug() << "IpcProcessTun2Socks::start()";
m_t2sProcess->setProgram(amnezia::permittedProcessPath(static_cast<amnezia::PermittedProcess>(amnezia::PermittedProcess::Tun2Socks)));
QString XrayConStr = "socks5://127.0.0.1:10808";
#ifdef Q_OS_WIN
QStringList arguments({"-device", "tun://tun2?guid={081A8A84-8D12-4DF5-B8C4-396D5B0053E4}", "-proxy", XrayConStr, "-tun-post-up",
QString("cmd /c netsh interface ip set address name=\"tun2\" static %1 255.255.255.255")
.arg(amnezia::protocols::xray::defaultLocalAddr)});
#endif
#ifdef Q_OS_LINUX
QStringList arguments({"-device", "tun://tun2", "-proxy", XrayConStr});
#endif
#ifdef Q_OS_MAC
QStringList arguments({"-device", "utun22", "-proxy", XrayConStr});
#endif
m_t2sProcess->setArguments(arguments);
if (Utils::processIsRunning(Utils::executable("tun2socks", false))) {
qDebug().noquote() << "kill previos tun2socks";
Utils::killProcessByName(Utils::executable("tun2socks", false));
}
m_t2sProcess->start();
connect(m_t2sProcess.data(), &QProcess::readyReadStandardOutput, this, [this]() {
QString line = m_t2sProcess.data()->readAllStandardOutput();
if (line.contains("[STACK] tun://") && line.contains("<-> socks5://127.0.0.1")) {
emit setConnectionState(Vpn::ConnectionState::Connected);
}
});
connect(m_t2sProcess.data(), QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) {
qDebug().noquote() << "tun2socks finished, exitCode, exiStatus" << exitCode << exitStatus;
emit setConnectionState(Vpn::ConnectionState::Disconnected);
if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) {
emit setConnectionState(Vpn::ConnectionState::Error);
}
});
m_t2sProcess->start();
m_t2sProcess->waitForStarted();
}
void IpcProcessTun2Socks::stop()
{
qDebug() << "IpcProcessTun2Socks::stop()";
m_t2sProcess->disconnect();
m_t2sProcess->kill();
m_t2sProcess->waitForFinished(3000);
}
#endif

View File

@@ -1,52 +0,0 @@
#ifndef IPCTUN2SOCKSPROCESS_H
#define IPCTUN2SOCKSPROCESS_H
#include <QObject>
#ifndef Q_OS_IOS
#include "rep_ipc_process_tun2socks_source.h"
namespace Vpn
{
Q_NAMESPACE
enum ConnectionState {
Unknown,
Disconnected,
Preparing,
Connecting,
Connected,
Disconnecting,
Reconnecting,
Error
};
Q_ENUM_NS(ConnectionState)
}
class IpcProcessTun2Socks : public IpcProcessTun2SocksSource
{
Q_OBJECT
public:
explicit IpcProcessTun2Socks(QObject *parent = nullptr);
virtual ~IpcProcessTun2Socks();
void start() override;
void stop() override;
signals:
private:
QSharedPointer<QProcess> m_t2sProcess;
};
#else
class IpcProcessTun2Socks : public QObject
{
Q_OBJECT
public:
explicit IpcProcessTun2Socks(QObject *parent = nullptr);
};
#endif
#endif // IPCTUN2SOCKSPROCESS_H

View File

@@ -6,7 +6,7 @@ project(${PROJECT})
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS DBus Core Network Widgets RemoteObjects Core5Compat) find_package(Qt6 REQUIRED COMPONENTS DBus Core Network Widgets RemoteObjects Core5Compat Concurrent)
qt_standard_project_setup() qt_standard_project_setup()
@@ -75,7 +75,6 @@ set(HEADERS
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.h
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.h
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipctun2socksprocess.h
${CMAKE_CURRENT_LIST_DIR}/localserver.h ${CMAKE_CURRENT_LIST_DIR}/localserver.h
${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.h ${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.h
${CMAKE_CURRENT_LIST_DIR}/router.h ${CMAKE_CURRENT_LIST_DIR}/router.h
@@ -97,7 +96,6 @@ set(SOURCES
${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp ${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserverprocess.cpp
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipctun2socksprocess.cpp
${CMAKE_CURRENT_LIST_DIR}/localserver.cpp ${CMAKE_CURRENT_LIST_DIR}/localserver.cpp
${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.cpp ${CMAKE_CURRENT_LIST_DIR}/../../common/logger/logger.cpp
${CMAKE_CURRENT_LIST_DIR}/main.cpp ${CMAKE_CURRENT_LIST_DIR}/main.cpp
@@ -353,7 +351,7 @@ include_directories(
add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES}) add_executable(${PROJECT} ${SOURCES} ${HEADERS} ${RESOURCES})
target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus ${LIBS}) target_link_libraries(${PROJECT} PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::RemoteObjects Qt6::Core5Compat Qt6::DBus Qt6::Concurrent ${LIBS})
target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>") target_compile_definitions(${PROJECT} PRIVATE "MZ_$<UPPER_CASE:${MZ_PLATFORM_NAME}>")
if(CMAKE_BUILD_TYPE STREQUAL "Debug") if(CMAKE_BUILD_TYPE STREQUAL "Debug")
@@ -389,7 +387,6 @@ endif()
qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_interface.rep)
qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep) qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_interface.rep)
qt_add_repc_sources(${PROJECT} ${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc_process_tun2socks.rep)
# copy deploy artifacts required to run the application to the debug build folder # copy deploy artifacts required to run the application to the debug build folder
if(WIN32) if(WIN32)

View File

@@ -33,18 +33,10 @@ KillSwitch* KillSwitch::instance()
bool KillSwitch::init() bool KillSwitch::init()
{ {
#ifdef Q_OS_LINUX #if defined(Q_OS_LINUX) || defined(Q_OS_MACOS)
if (!LinuxFirewall::isInstalled()) {
LinuxFirewall::install();
}
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
#endif
#ifdef Q_OS_MACOS
if (!MacOSFirewall::isInstalled()) {
MacOSFirewall::install();
}
m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr)); m_appSettigns = QSharedPointer<SecureQSettings>(new SecureQSettings(ORGANIZATION_NAME, APPLICATION_NAME, nullptr));
#endif #endif
if (isStrictKillSwitchEnabled()) { if (isStrictKillSwitchEnabled()) {
return disableAllTraffic(); return disableAllTraffic();
} }

View File

@@ -40,7 +40,6 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
if (!m_isRemotingEnabled) { if (!m_isRemotingEnabled) {
m_isRemotingEnabled = true; m_isRemotingEnabled = true;
m_serverNode.enableRemoting(&m_ipcServer); m_serverNode.enableRemoting(&m_ipcServer);
m_serverNode.enableRemoting(&m_tun2socks);
} }
}); });
@@ -51,8 +50,8 @@ LocalServer::LocalServer(QObject *parent) : QObject(parent),
} }
m_networkWatcher.initialize(); m_networkWatcher.initialize();
connect(&m_networkWatcher, &NetworkWatcher::sleepMode, &m_ipcServer, &IpcServer::networkChange); connect(&m_networkWatcher, &NetworkWatcher::networkChanged, &m_ipcServer, &IpcServer::networkChanged);
connect(&m_networkWatcher, &NetworkWatcher::networkChange, &m_ipcServer, &IpcServer::networkChange); connect(&m_networkWatcher, &NetworkWatcher::wakeup, &m_ipcServer, &IpcServer::wakeup);
KillSwitch::instance()->init(); KillSwitch::instance()->init();
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX

View File

@@ -38,7 +38,6 @@ public:
~LocalServer(); ~LocalServer();
QSharedPointer<QLocalServer> m_server; QSharedPointer<QLocalServer> m_server;
IpcServer m_ipcServer; IpcServer m_ipcServer;
IpcProcessTun2Socks m_tun2socks;
QRemoteObjectHost m_serverNode; QRemoteObjectHost m_serverNode;
bool m_isRemotingEnabled = false; bool m_isRemotingEnabled = false;

View File

@@ -66,6 +66,9 @@ void Router::resetIpStack()
bool Router::createTun(const QString &dev, const QString &subnet) bool Router::createTun(const QString &dev, const QString &subnet)
{ {
#ifdef Q_OS_WIN
return RouterWin::Instance().createTun(dev, subnet);
#endif
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
return RouterLinux::Instance().createTun(dev, subnet); return RouterLinux::Instance().createTun(dev, subnet);
#endif #endif

View File

@@ -5,6 +5,7 @@
#include <tchar.h> #include <tchar.h>
#include <QProcess> #include <QProcess>
#include <QtConcurrent>
#include <core/networkUtilities.h> #include <core/networkUtilities.h>
@@ -308,6 +309,77 @@ void RouterWin::resetIpStack()
} }
} }
bool RouterWin::createTun(const QString &dev, const QString &subnet)
{
NET_LUID luid;
DWORD res = ConvertInterfaceAliasToLuid(reinterpret_cast<const wchar_t*>(dev.utf16()), &luid);
if (res != NO_ERROR) {
qCritical() << "Failed to convert luid: " << res;
return false;
}
HANDLE hEvent = CreateEvent(nullptr, true, false, nullptr);
if (!hEvent) {
qCritical() << "Failed to allocate event object";
return false;
}
auto _guardEvent = qScopeGuard([hEvent](){ CloseHandle(hEvent); });
struct {
HANDLE hEvent;
NET_LUID luid;
const QString &subnet;
bool found;
} ctx = { .hEvent = hEvent, .luid = luid, .subnet = subnet, .found = false };
auto cb = [](void *priv, MIB_UNICASTIPADDRESS_ROW *row, MIB_NOTIFICATION_TYPE NotificationType) {
auto* c = reinterpret_cast<decltype(ctx)*>(priv);
if (row != nullptr && row->InterfaceLuid.Value == c->luid.Value && row->Address.si_family == AF_INET) {
char ip[INET_ADDRSTRLEN];
inet_ntop(row->Address.Ipv4.sin_family, &row->Address.Ipv4.sin_addr, ip, INET_ADDRSTRLEN);
if (c->subnet == ip) {
c->found = true;
SetEvent(c->hEvent);
}
}
};
HANDLE hNotif;
res = NotifyUnicastIpAddressChange(AF_INET, cb, &ctx, false, &hNotif);
if (res != NO_ERROR) {
qCritical() << "Failed to subscribe to interface change";
return false;
}
auto _guardNotif = qScopeGuard([hNotif](){ CancelMibChangeNotify2(hNotif); });
MIB_UNICASTIPADDRESS_ROW row;
InitializeUnicastIpAddressEntry(&row);
row.InterfaceLuid = luid;
row.Address.si_family = AF_INET;
inet_pton(AF_INET, subnet.toStdString().c_str(), &row.Address.Ipv4.sin_addr);
row.OnLinkPrefixLength = 32;
row.ValidLifetime = 0xffffffff;
row.PreferredLifetime = 0xffffffff;
row.DadState = IpDadStatePreferred;
res = CreateUnicastIpAddressEntry(&row);
if (res != NO_ERROR && res != ERROR_OBJECT_ALREADY_EXISTS) {
qDebug() << "Failed to create IP address:" << res;
return false;
}
res = WaitForSingleObject(hEvent, 10000);
if (res == WAIT_TIMEOUT) {
qCritical() << "Timeout of waiting for IP assignment for " << dev << " device";
return false;
}
return ctx.found;
}
void RouterWin::suspendWcmSvc(bool suspend) void RouterWin::suspendWcmSvc(bool suspend)
{ {
if (suspend == m_suspended) return; if (suspend == m_suspended) return;
@@ -465,11 +537,19 @@ bool RouterWin::StopRoutingIpv6()
qDebug() << "RouterWin::StopRoutingIpv6"; qDebug() << "RouterWin::StopRoutingIpv6";
if (auto loopback = findLoopbackIface(); loopback.isValid()) { if (auto loopback = findLoopbackIface(); loopback.isValid()) {
for (auto subnet : kIpv6Subnets) { QFuture<bool> res = QtConcurrent::mappedReduced(kIpv6Subnets, [loopback](const QString &subnet) -> bool {
QProcess{}.execute("netsh", { "interface", "ipv6", "add", "route", subnet, QString("interface=%1").arg(loopback.index()), "metric=0", "store=active" }); int res = QProcess::execute("netsh", { "interface", "ipv6", "add", "route", subnet, QString("interface=%1").arg(loopback.index()), "metric=0", "store=active" });
} return res == 0;
},
[](bool &result, bool success) {
result = result && success;
}, true);
res.waitForFinished();
return res.result();
} }
return true;
return false;
} }
bool RouterWin::StartRoutingIpv6() bool RouterWin::StartRoutingIpv6()
@@ -477,9 +557,14 @@ bool RouterWin::StartRoutingIpv6()
qDebug() << "RouterWin::StartRoutingIpv6"; qDebug() << "RouterWin::StartRoutingIpv6";
if (auto loopback = findLoopbackIface(); loopback.isValid()) { if (auto loopback = findLoopbackIface(); loopback.isValid()) {
for (auto subnet : kIpv6Subnets) { QFuture<bool> res = QtConcurrent::mappedReduced(kIpv6Subnets, [loopback](const QString &subnet) -> bool {
QProcess{}.execute("netsh", { "interface", "ipv6", "delete", "route", subnet, QString("interface=%1").arg(loopback.index()) }); int res = QProcess::execute("netsh", { "interface", "ipv6", "delete", "route", subnet, QString("interface=%1").arg(loopback.index()) });
} return res == 0;
},
[](bool &result, bool success) {
result = result && success;
}, true);
} }
return true;
return false;
} }

View File

@@ -45,6 +45,7 @@ public:
bool StartRoutingIpv6(); bool StartRoutingIpv6();
bool StopRoutingIpv6(); bool StopRoutingIpv6();
bool createTun(const QString &dev, const QString &subnet);
void suspendWcmSvc(bool suspend); void suspendWcmSvc(bool suspend);
bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers); bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers);
bool restoreResolvers(); bool restoreResolvers();

View File

@@ -27,7 +27,7 @@
#include <sys/socket.h> #include <sys/socket.h>
#endif #endif
void Xray::startXray(const QString &cfg) bool Xray::startXray(const QString &cfg)
{ {
qDebug() << "Xray::startXray()"; qDebug() << "Xray::startXray()";
@@ -40,34 +40,38 @@ void Xray::startXray(const QString &cfg)
if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) { if (auto err = amnezia_xray_setsockcallback(ctxSockCallback, this); err != nullptr) {
qDebug() << "[xray] sockopt failed: " << err; qDebug() << "[xray] sockopt failed: " << err;
free(err); amnezia_xray_free(err);
return; return false;
}
QByteArray bytes = cfg.toUtf8();
if (auto err = amnezia_xray_configure(bytes.data()); err != nullptr) {
qDebug() << "[xray] configuration failed: " << err;
free(err);
return;
} }
amnezia_xray_setloghandler(ctxLogHandler, this); amnezia_xray_setloghandler(ctxLogHandler, this);
QByteArray bytes = cfg.toUtf8();
if (auto err = amnezia_xray_configure(bytes.data()); err != nullptr) {
qDebug() << "[xray] configuration failed: " << err;
amnezia_xray_free(err);
return false;
}
if (auto err = amnezia_xray_start(); err != nullptr) { if (auto err = amnezia_xray_start(); err != nullptr) {
qDebug() << "[xray] failed to start: " << err; qDebug() << "[xray] failed to start: " << err;
free(err); amnezia_xray_free(err);
return; return false;
} }
return true;
} }
void Xray::stopXray() bool Xray::stopXray()
{ {
qDebug() << "Xray::stopXray()"; qDebug() << "Xray::stopXray()";
if (auto err = amnezia_xray_stop(); err != nullptr) { if (auto err = amnezia_xray_stop(); err != nullptr) {
qDebug() << "[xray] failed to stop: " << err; qDebug() << "[xray] failed to stop: " << err;
free(err); amnezia_xray_free(err);
return; return false;
} }
return true;
} }
void Xray::logHandler(char* str) void Xray::logHandler(char* str)

View File

@@ -12,8 +12,8 @@ public:
return instance; return instance;
} }
void startXray(const QString& cfg); bool startXray(const QString& cfg);
void stopXray(); bool stopXray();
private: private:
static void ctxSockCallback(uintptr_t fd, void* ctx) { static void ctxSockCallback(uintptr_t fd, void* ctx) {