mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-18 00:45:58 +03:00
Compare commits
1 Commits
fix/clicka
...
bugfix/qr-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0b46467e5 |
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
@@ -233,7 +233,7 @@ jobs:
|
||||
- name: 'Setup xcode'
|
||||
uses: maxim-lobanov/setup-xcode@v1
|
||||
with:
|
||||
xcode-version: '14.3.1'
|
||||
xcode-version: '13.4'
|
||||
|
||||
- name: 'Install Qt'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
|
||||
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
||||
|
||||
set(PROJECT AmneziaVPN)
|
||||
|
||||
project(${PROJECT} VERSION 4.5.3.0
|
||||
project(${PROJECT} VERSION 4.5.2.0
|
||||
DESCRIPTION "AmneziaVPN"
|
||||
HOMEPAGE_URL "https://amnezia.org/"
|
||||
)
|
||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||
|
||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||
set(APP_ANDROID_VERSION_CODE 52)
|
||||
set(APP_ANDROID_VERSION_CODE 51)
|
||||
|
||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||
set(MZ_PLATFORM_NAME "linux")
|
||||
|
||||
@@ -69,8 +69,6 @@ set(AMNEZIAVPN_TS_FILES
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_uk_UA.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ur_PK.ts
|
||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_hi_IN.ts
|
||||
)
|
||||
|
||||
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||
@@ -127,6 +125,7 @@ set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/pages.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||
@@ -151,16 +150,11 @@ include_directories(mozilla/models)
|
||||
|
||||
if(NOT IOS)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||
)
|
||||
endif()
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
||||
@@ -172,6 +166,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||
@@ -192,16 +187,11 @@ endif()
|
||||
|
||||
if(NOT IOS)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
|
||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(NOT ANDROID)
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
file(GLOB COMMON_FILES_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
|
||||
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
||||
|
||||
|
||||
@@ -55,7 +55,6 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
|
||||
#endif
|
||||
|
||||
m_settings = std::shared_ptr<Settings>(new Settings);
|
||||
m_nam = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
AmneziaApplication::~AmneziaApplication()
|
||||
@@ -143,7 +142,6 @@ void AmneziaApplication::init()
|
||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||
#endif
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||
|
||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||
@@ -151,11 +149,10 @@ void AmneziaApplication::init()
|
||||
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||
&ConnectionController::openConnection);
|
||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||
&ConnectionController::closeConnection);
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||
#endif
|
||||
|
||||
m_engine->load(url);
|
||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||
@@ -363,22 +360,16 @@ void AmneziaApplication::initControllers()
|
||||
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||
|
||||
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this, [this](const QString &errorMessage) {
|
||||
connect(m_connectionController.get(), &ConnectionController::connectionErrorOccurred, this, [this](const QString &errorMessage) {
|
||||
emit m_pageController->showErrorMessage(errorMessage);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), qOverload<ErrorCode>(&ConnectionController::connectionErrorOccurred), this, [this](ErrorCode errorCode) {
|
||||
emit m_pageController->showErrorMessage(errorCode);
|
||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
||||
});
|
||||
|
||||
connect(m_connectionController.get(), &ConnectionController::connectButtonClicked, m_connectionController.get(),
|
||||
&ConnectionController::toggleConnection, Qt::QueuedConnection);
|
||||
|
||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(), &ConnectionController::onTranslationsUpdated);
|
||||
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings, m_languageModel));
|
||||
m_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||
|
||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
#define AMNEZIA_APPLICATION_H
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QThread>
|
||||
@@ -27,9 +26,7 @@
|
||||
#include "ui/models/containers_model.h"
|
||||
#include "ui/models/languageModel.h"
|
||||
#include "ui/models/protocols/cloakConfigModel.h"
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include "ui/notificationhandler.h"
|
||||
#endif
|
||||
#include "ui/notificationhandler.h"
|
||||
#ifdef Q_OS_WINDOWS
|
||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||
#endif
|
||||
@@ -76,7 +73,6 @@ public:
|
||||
bool parseCommands();
|
||||
|
||||
QQmlApplicationEngine *qmlEngine() const;
|
||||
QNetworkAccessManager *manager() { return m_nam; }
|
||||
|
||||
signals:
|
||||
void translationsUpdated();
|
||||
@@ -117,9 +113,7 @@ private:
|
||||
|
||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||
QThread m_vpnConnectionThread;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||
#endif
|
||||
|
||||
QScopedPointer<ConnectionController> m_connectionController;
|
||||
QScopedPointer<PageController> m_pageController;
|
||||
@@ -130,8 +124,6 @@ private:
|
||||
QScopedPointer<SitesController> m_sitesController;
|
||||
QScopedPointer<SystemController> m_systemController;
|
||||
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||
|
||||
QNetworkAccessManager *m_nam;
|
||||
};
|
||||
|
||||
#endif // AMNEZIA_APPLICATION_H
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.amnezia.vpn.protocol.cloak
|
||||
import android.util.Base64
|
||||
import net.openvpn.ovpn3.ClientAPI_Config
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
||||
import org.amnezia.vpn.protocol.openvpn.OpenVpnConfig
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
@@ -51,6 +54,13 @@ class Cloak : OpenVpn() {
|
||||
return openVpnConfig
|
||||
}
|
||||
|
||||
override fun configPluggableTransport(configBuilder: OpenVpnConfig.Builder, config: JSONObject) {
|
||||
// exclude remote server ip from vpn routes
|
||||
val remoteServer = config.getString("hostName")
|
||||
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
|
||||
configBuilder.excludeRoute(remoteServerAddress)
|
||||
}
|
||||
|
||||
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
|
||||
cloakConfigJson.put("NumConn", 1)
|
||||
cloakConfigJson.put("ProxyMethod", "openvpn")
|
||||
|
||||
@@ -13,9 +13,7 @@ import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.Statistics
|
||||
import org.amnezia.vpn.protocol.VpnStartException
|
||||
import org.amnezia.vpn.util.net.InetNetwork
|
||||
import org.amnezia.vpn.util.net.getLocalNetworks
|
||||
import org.amnezia.vpn.util.net.parseInetAddress
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
@@ -79,12 +77,6 @@ open class OpenVpn : Protocol() {
|
||||
if (evalConfig.error) {
|
||||
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
|
||||
}
|
||||
|
||||
// exclude remote server ip from vpn routes
|
||||
val remoteServer = config.getString("hostName")
|
||||
val remoteServerAddress = InetNetwork(parseInetAddress(remoteServer))
|
||||
configBuilder.excludeRoute(remoteServerAddress)
|
||||
|
||||
configPluggableTransport(configBuilder, config)
|
||||
configBuilder.configSplitTunneling(config)
|
||||
configBuilder.configAppSplitTunneling(config)
|
||||
|
||||
@@ -105,27 +105,25 @@ abstract class Protocol {
|
||||
vpnBuilder.addSearchDomain(it)
|
||||
}
|
||||
|
||||
for ((inetNetwork, include) in config.routes) {
|
||||
if (include) {
|
||||
Log.d(TAG, "addRoute: $inetNetwork")
|
||||
vpnBuilder.addRoute(inetNetwork)
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
Log.d(TAG, "excludeRoute: $inetNetwork")
|
||||
vpnBuilder.excludeRoute(inetNetwork)
|
||||
} else {
|
||||
Log.e(TAG, "Trying to exclude route $inetNetwork on old Android")
|
||||
}
|
||||
for (addr in config.routes) {
|
||||
Log.d(TAG, "addRoute: $addr")
|
||||
vpnBuilder.addRoute(addr)
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
for (addr in config.excludedRoutes) {
|
||||
Log.d(TAG, "excludeRoute: $addr")
|
||||
vpnBuilder.excludeRoute(addr)
|
||||
}
|
||||
}
|
||||
|
||||
for (app in config.includedApplications) {
|
||||
Log.d(TAG, "addAllowedApplication")
|
||||
Log.d(TAG, "addAllowedApplication: $app")
|
||||
vpnBuilder.addAllowedApplication(app)
|
||||
}
|
||||
|
||||
for (app in config.excludedApplications) {
|
||||
Log.d(TAG, "addDisallowedApplication")
|
||||
Log.d(TAG, "addDisallowedApplication: $app")
|
||||
vpnBuilder.addDisallowedApplication(app)
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,8 @@ open class ProtocolConfig protected constructor(
|
||||
val addresses: Set<InetNetwork>,
|
||||
val dnsServers: Set<InetAddress>,
|
||||
val searchDomain: String?,
|
||||
val routes: Set<Route>,
|
||||
val routes: Set<InetNetwork>,
|
||||
val excludedRoutes: Set<InetNetwork>,
|
||||
val includedAddresses: Set<InetNetwork>,
|
||||
val excludedAddresses: Set<InetNetwork>,
|
||||
val includedApplications: Set<String>,
|
||||
@@ -28,6 +29,7 @@ open class ProtocolConfig protected constructor(
|
||||
builder.dnsServers,
|
||||
builder.searchDomain,
|
||||
builder.routes,
|
||||
builder.excludedRoutes,
|
||||
builder.includedAddresses,
|
||||
builder.excludedAddresses,
|
||||
builder.includedApplications,
|
||||
@@ -41,7 +43,8 @@ open class ProtocolConfig protected constructor(
|
||||
open class Builder(blockingMode: Boolean) {
|
||||
internal val addresses: MutableSet<InetNetwork> = hashSetOf()
|
||||
internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
|
||||
internal val routes: MutableSet<Route> = mutableSetOf()
|
||||
internal val routes: MutableSet<InetNetwork> = hashSetOf()
|
||||
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
|
||||
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
||||
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
||||
internal val includedApplications: MutableSet<String> = hashSetOf()
|
||||
@@ -74,21 +77,13 @@ open class ProtocolConfig protected constructor(
|
||||
|
||||
fun setSearchDomain(domain: String) = apply { this.searchDomain = domain }
|
||||
|
||||
fun addRoute(route: InetNetwork) = apply { this.routes += Route(route, true) }
|
||||
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, true) } }
|
||||
|
||||
fun excludeRoute(route: InetNetwork) = apply { this.routes += Route(route, false) }
|
||||
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, false) } }
|
||||
|
||||
fun removeRoute(route: InetNetwork) = apply { this.routes.removeIf { it.inetNetwork == route } }
|
||||
fun addRoute(route: InetNetwork) = apply { this.routes += route }
|
||||
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes }
|
||||
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
|
||||
fun clearRoutes() = apply { this.routes.clear() }
|
||||
|
||||
fun prependRoutes(block: Builder.() -> Unit) = apply {
|
||||
val savedRoutes = mutableListOf<Route>().apply { addAll(routes) }
|
||||
routes.clear()
|
||||
block()
|
||||
routes.addAll(savedRoutes)
|
||||
}
|
||||
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
|
||||
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
|
||||
|
||||
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
|
||||
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
|
||||
@@ -122,46 +117,37 @@ open class ProtocolConfig protected constructor(
|
||||
// remove default routes, if any
|
||||
removeRoute(InetNetwork("0.0.0.0", 0))
|
||||
removeRoute(InetNetwork("::", 0))
|
||||
removeRoute(InetNetwork("2000::", 3))
|
||||
prependRoutes {
|
||||
addRoutes(includedAddresses)
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
// for older versions of Android, add the default route to the excluded routes
|
||||
// to correctly build the excluded subnets list later
|
||||
excludeRoute(InetNetwork("0.0.0.0", 0))
|
||||
}
|
||||
addRoutes(includedAddresses)
|
||||
} else if (excludedAddresses.isNotEmpty()) {
|
||||
prependRoutes {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
// default routes are required for split tunneling in newer versions of Android
|
||||
addRoute(InetNetwork("0.0.0.0", 0))
|
||||
addRoute(InetNetwork("2000::", 3))
|
||||
excludeRoutes(excludedAddresses)
|
||||
addRoute(InetNetwork("::", 0))
|
||||
}
|
||||
excludeRoutes(excludedAddresses)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processRoutes() {
|
||||
// replace ::/0 as it may cause LAN connection issues
|
||||
val ipv6DefaultRoute = InetNetwork("::", 0)
|
||||
if (routes.removeIf { it.include && it.inetNetwork == ipv6DefaultRoute }) {
|
||||
prependRoutes {
|
||||
addRoute(InetNetwork("2000::", 3))
|
||||
}
|
||||
}
|
||||
// for older versions of Android, build a list of subnets without excluded routes
|
||||
// and add them to routes
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && routes.any { !it.include }) {
|
||||
private fun processExcludedRoutes() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && excludedRoutes.isNotEmpty()) {
|
||||
// todo: rewrite, taking into account the current routes
|
||||
// for older versions of Android, build a list of subnets without excluded routes
|
||||
// and add them to routes
|
||||
val ipRangeSet = IpRangeSet()
|
||||
routes.forEach {
|
||||
if (it.include) ipRangeSet.add(IpRange(it.inetNetwork))
|
||||
else ipRangeSet.remove(IpRange(it.inetNetwork))
|
||||
}
|
||||
ipRangeSet.remove(IpRange("127.0.0.0", 8))
|
||||
ipRangeSet.remove(IpRange("::1", 128))
|
||||
routes.clear()
|
||||
excludedRoutes.forEach {
|
||||
ipRangeSet.remove(IpRange(it))
|
||||
}
|
||||
// remove default routes, if any
|
||||
removeRoute(InetNetwork("0.0.0.0", 0))
|
||||
removeRoute(InetNetwork("::", 0))
|
||||
ipRangeSet.subnets().forEach(::addRoute)
|
||||
}
|
||||
// filter ipv4 and ipv6 loopback addresses
|
||||
val ipv6Loopback = InetNetwork("::1", 128)
|
||||
routes.removeIf {
|
||||
it.include &&
|
||||
if (it.inetNetwork.isIpv4) it.inetNetwork.address.address[0] == 127.toByte()
|
||||
else it.inetNetwork == ipv6Loopback
|
||||
addRoute(InetNetwork("2000::", 3))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +165,7 @@ open class ProtocolConfig protected constructor(
|
||||
|
||||
protected fun configBuild() {
|
||||
processSplitTunneling()
|
||||
processRoutes()
|
||||
processExcludedRoutes()
|
||||
validate()
|
||||
}
|
||||
|
||||
@@ -191,5 +177,3 @@ open class ProtocolConfig protected constructor(
|
||||
Builder(blockingMode).apply(block).build()
|
||||
}
|
||||
}
|
||||
|
||||
data class Route(val inetNetwork: InetNetwork, val include: Boolean)
|
||||
|
||||
@@ -1,26 +1,12 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="disconnected">Не подключено</string>
|
||||
<string name="connected">Подключено</string>
|
||||
<string name="connecting">Подключение…</string>
|
||||
<string name="disconnecting">Отключение…</string>
|
||||
<string name="reconnecting">Переподключение…</string>
|
||||
<string name="connect">Подключиться</string>
|
||||
<string name="disconnect">Отключиться</string>
|
||||
<string name="ok">ОК</string>
|
||||
<string name="connecting">Подключение</string>
|
||||
<string name="disconnecting">Отключение</string>
|
||||
<string name="cancel">Отмена</string>
|
||||
<string name="yes">Да</string>
|
||||
<string name="no">Нет</string>
|
||||
|
||||
<string name="ok">ОК</string>
|
||||
<string name="vpnGranted">VPN-подключение разрешено</string>
|
||||
<string name="vpnDenied">VPN-подключение запрещено</string>
|
||||
<string name="vpnSetupFailed">Ошибка настройки VPN</string>
|
||||
<string name="vpnSetupFailedMessage">Чтобы подключиться к AmneziaVPN необходимо:\n\n- Разрешить приложению подключаться к сети VPN\n- Отключить функцию \"Постоянная VPN\" для всех остальных VPN-приложений в системных настройках VPN</string>
|
||||
<string name="openVpnSettings">Открыть настройки VPN</string>
|
||||
|
||||
<string name="notificationChannelDescription">Уведомления сервиса AmneziaVPN</string>
|
||||
<string name="notificationDialogTitle">Сервис AmneziaVPN</string>
|
||||
<string name="notificationDialogMessage">Показывать статус VPN в строке состояния?</string>
|
||||
<string name="notificationSettingsDialogTitle">Настройки уведомлений</string>
|
||||
<string name="notificationSettingsDialogMessage">Для показа уведомлений необходимо включить уведомления в системных настройках</string>
|
||||
<string name="openNotificationSettings">Открыть настройки уведомлений</string>
|
||||
</resources>
|
||||
@@ -1,26 +1,12 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="disconnected">Not connected</string>
|
||||
<string name="connected">Connected</string>
|
||||
<string name="connecting">Connecting…</string>
|
||||
<string name="disconnecting">Disconnecting…</string>
|
||||
<string name="reconnecting">Reconnecting…</string>
|
||||
<string name="connect">Connect</string>
|
||||
<string name="disconnect">Disconnect</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="connecting">Connecting</string>
|
||||
<string name="disconnecting">Disconnecting</string>
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
|
||||
<string name="ok">OK</string>
|
||||
<string name="vpnGranted">VPN permission granted</string>
|
||||
<string name="vpnDenied">VPN permission denied</string>
|
||||
<string name="vpnSetupFailed">VPN setup error</string>
|
||||
<string name="vpnSetupFailedMessage">To connect to AmneziaVPN, please do the following:\n\n- Allow the app to set up a VPN connection\n- Disable Always-on VPN for any other VPN app in the VPN system settings</string>
|
||||
<string name="openVpnSettings">Open VPN settings</string>
|
||||
|
||||
<string name="notificationChannelDescription">AmneziaVPN service notification</string>
|
||||
<string name="notificationDialogTitle">AmneziaVPN service</string>
|
||||
<string name="notificationDialogMessage">Show the VPN state in the status bar?</string>
|
||||
<string name="notificationSettingsDialogTitle">Notification settings</string>
|
||||
<string name="notificationSettingsDialogMessage">To show notifications, you must enable notifications in the system settings</string>
|
||||
<string name="openNotificationSettings">Open notification settings</string>
|
||||
</resources>
|
||||
@@ -1,9 +1,6 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest
|
||||
import android.app.AlertDialog
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.Intent.EXTRA_MIME_TYPES
|
||||
@@ -11,8 +8,8 @@ import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.net.Uri
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.IBinder
|
||||
@@ -24,7 +21,6 @@ import android.view.WindowManager.LayoutParams
|
||||
import android.webkit.MimeTypeMap
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.content.ContextCompat
|
||||
import java.io.IOException
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
@@ -42,7 +38,6 @@ import org.amnezia.vpn.protocol.getStatistics
|
||||
import org.amnezia.vpn.protocol.getStatus
|
||||
import org.amnezia.vpn.qt.QtAndroidController
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.qtproject.qt.android.bindings.QtActivity
|
||||
|
||||
private const val TAG = "AmneziaActivity"
|
||||
@@ -51,9 +46,6 @@ const val ACTIVITY_MESSENGER_NAME = "Activity"
|
||||
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
|
||||
private const val CREATE_FILE_ACTION_CODE = 2
|
||||
private const val OPEN_FILE_ACTION_CODE = 3
|
||||
private const val CHECK_NOTIFICATION_PERMISSION_ACTION_CODE = 4
|
||||
|
||||
private const val PREFS_NOTIFICATION_PERMISSION_ASKED = "NOTIFICATION_PERMISSION_ASKED"
|
||||
|
||||
class AmneziaActivity : QtActivity() {
|
||||
|
||||
@@ -62,11 +54,8 @@ class AmneziaActivity : QtActivity() {
|
||||
private var isWaitingStatus = true
|
||||
private var isServiceConnected = false
|
||||
private var isInBoundState = false
|
||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||
|
||||
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||
private var tmpFileContentToSave: String = ""
|
||||
|
||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||
object : Handler(Looper.getMainLooper()) {
|
||||
@@ -146,6 +135,10 @@ class AmneziaActivity : QtActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private data class CheckVpnPermissionCallbacks(val onSuccess: () -> Unit, val onFail: () -> Unit)
|
||||
|
||||
private var checkVpnPermissionCallbacks: CheckVpnPermissionCallbacks? = null
|
||||
|
||||
/**
|
||||
* Activity overloaded methods
|
||||
*/
|
||||
@@ -160,30 +153,9 @@ class AmneziaActivity : QtActivity() {
|
||||
doBindService()
|
||||
}
|
||||
)
|
||||
registerBroadcastReceivers()
|
||||
intent?.let(::processIntent)
|
||||
}
|
||||
|
||||
private fun registerBroadcastReceivers() {
|
||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
registerBroadcastReceiver(
|
||||
arrayOf(
|
||||
NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
|
||||
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
||||
)
|
||||
) {
|
||||
Log.d(
|
||||
TAG, "Notification state changed: ${it?.action}, blocked = " +
|
||||
"${it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)}"
|
||||
)
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onNotificationStateChanged()
|
||||
}
|
||||
}
|
||||
} else null
|
||||
}
|
||||
|
||||
override fun onNewIntent(intent: Intent?) {
|
||||
super.onNewIntent(intent)
|
||||
Log.d(TAG, "onNewIntent: $intent")
|
||||
@@ -221,46 +193,50 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy Amnezia activity")
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
notificationStateReceiver = null
|
||||
mainScope.cancel()
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
Log.d(TAG, "Process activity result, code: ${actionCodeToString(requestCode)}, " +
|
||||
"resultCode: $resultCode, data: $data")
|
||||
actionResultHandlers[requestCode]?.let { handler ->
|
||||
when (resultCode) {
|
||||
RESULT_OK -> handler.onSuccess(data)
|
||||
else -> handler.onFail(data)
|
||||
when (requestCode) {
|
||||
CREATE_FILE_ACTION_CODE -> {
|
||||
when (resultCode) {
|
||||
RESULT_OK -> {
|
||||
data?.data?.let { uri ->
|
||||
alterDocument(uri)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
handler.onAny(data)
|
||||
actionResultHandlers.remove(requestCode)
|
||||
} ?: super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private fun startActivityForResult(intent: Intent, requestCode: Int, handler: ActivityResultHandler) {
|
||||
actionResultHandlers[requestCode] = handler
|
||||
startActivityForResult(intent, requestCode)
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||
Log.d(TAG, "Process permission result, code: ${actionCodeToString(requestCode)}, " +
|
||||
"permissions: ${permissions.contentToString()}, results: ${grantResults.contentToString()}")
|
||||
permissionRequestHandlers[requestCode]?.let { handler ->
|
||||
if (grantResults.isNotEmpty()) {
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) handler.onSuccess()
|
||||
else handler.onFail()
|
||||
OPEN_FILE_ACTION_CODE -> {
|
||||
when (resultCode) {
|
||||
RESULT_OK -> data?.data?.toString() ?: ""
|
||||
else -> ""
|
||||
}.let { uri ->
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
}
|
||||
}
|
||||
handler.onAny()
|
||||
permissionRequestHandlers.remove(requestCode)
|
||||
} ?: super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||
}
|
||||
|
||||
private fun requestPermission(permission: String, requestCode: Int, handler: PermissionRequestHandler) {
|
||||
permissionRequestHandlers[requestCode] = handler
|
||||
requestPermissions(arrayOf(permission), requestCode)
|
||||
CHECK_VPN_PERMISSION_ACTION_CODE -> {
|
||||
when (resultCode) {
|
||||
RESULT_OK -> {
|
||||
Log.d(TAG, "Vpn permission granted")
|
||||
Toast.makeText(this, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
checkVpnPermissionCallbacks?.run { onSuccess() }
|
||||
}
|
||||
|
||||
else -> {
|
||||
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
||||
showOnVpnPermissionRejectDialog()
|
||||
checkVpnPermissionCallbacks?.run { onFail() }
|
||||
}
|
||||
}
|
||||
checkVpnPermissionCallbacks = null
|
||||
}
|
||||
|
||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -292,26 +268,22 @@ class AmneziaActivity : QtActivity() {
|
||||
/**
|
||||
* Methods of starting and stopping VpnService
|
||||
*/
|
||||
private fun checkVpnPermissionAndStart(vpnConfig: String) {
|
||||
checkVpnPermission(
|
||||
onSuccess = { startVpn(vpnConfig) },
|
||||
onFail = QtAndroidController::onVpnPermissionRejected
|
||||
)
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun checkVpnPermission(onPermissionGranted: () -> Unit) {
|
||||
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
|
||||
Log.d(TAG, "Check VPN permission")
|
||||
VpnService.prepare(applicationContext)?.let { intent ->
|
||||
startActivityForResult(intent, CHECK_VPN_PERMISSION_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
Log.d(TAG, "Vpn permission granted")
|
||||
Toast.makeText(this@AmneziaActivity, resources.getText(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||
onPermissionGranted()
|
||||
},
|
||||
onFail = {
|
||||
Log.w(TAG, "Vpn permission denied")
|
||||
showOnVpnPermissionRejectDialog()
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onVpnPermissionRejected()
|
||||
}
|
||||
}
|
||||
))
|
||||
} ?: onPermissionGranted()
|
||||
VpnService.prepare(applicationContext)?.let {
|
||||
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
|
||||
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
|
||||
return
|
||||
}
|
||||
onSuccess()
|
||||
}
|
||||
|
||||
private fun showOnVpnPermissionRejectDialog() {
|
||||
@@ -325,44 +297,6 @@ class AmneziaActivity : QtActivity() {
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun checkNotificationPermission(onChecked: () -> Unit) {
|
||||
Log.d(TAG, "Check notification permission")
|
||||
if (
|
||||
!isNotificationPermissionGranted() &&
|
||||
!Prefs.load<Boolean>(PREFS_NOTIFICATION_PERMISSION_ASKED)
|
||||
) {
|
||||
showNotificationPermissionDialog(onChecked)
|
||||
} else {
|
||||
onChecked()
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
|
||||
private fun showNotificationPermissionDialog(onChecked: () -> Unit) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.notificationDialogTitle)
|
||||
.setMessage(R.string.notificationDialogMessage)
|
||||
.setNegativeButton(R.string.no) { _, _ ->
|
||||
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
|
||||
onChecked()
|
||||
}
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
val saveAsked: () -> Unit = {
|
||||
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
|
||||
}
|
||||
requestPermission(
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
CHECK_NOTIFICATION_PERMISSION_ACTION_CODE,
|
||||
PermissionRequestHandler(
|
||||
onSuccess = saveAsked,
|
||||
onFail = saveAsked,
|
||||
onAny = onChecked
|
||||
)
|
||||
)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun startVpn(vpnConfig: String) {
|
||||
if (isServiceConnected) {
|
||||
@@ -388,21 +322,28 @@ class AmneziaActivity : QtActivity() {
|
||||
Intent(this, AmneziaVpnService::class.java).apply {
|
||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||
}.also {
|
||||
try {
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
QtAndroidController.onServiceError()
|
||||
}
|
||||
ContextCompat.startForegroundService(this, it)
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun disconnectFromVpn() {
|
||||
Log.d(TAG, "Disconnect from VPN")
|
||||
vpnServiceMessenger.send(Action.DISCONNECT)
|
||||
}
|
||||
|
||||
// saving file
|
||||
private fun alterDocument(uri: Uri) {
|
||||
try {
|
||||
contentResolver.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(tmpFileContentToSave) }
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
tmpFileContentToSave = ""
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods called by Qt
|
||||
*/
|
||||
@@ -416,11 +357,7 @@ class AmneziaActivity : QtActivity() {
|
||||
fun start(vpnConfig: String) {
|
||||
Log.v(TAG, "Start VPN")
|
||||
mainScope.launch {
|
||||
checkVpnPermission {
|
||||
checkNotificationPermission {
|
||||
startVpn(vpnConfig)
|
||||
}
|
||||
}
|
||||
checkVpnPermissionAndStart(vpnConfig)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -452,26 +389,14 @@ class AmneziaActivity : QtActivity() {
|
||||
fun saveFile(fileName: String, data: String) {
|
||||
Log.d(TAG, "Save file $fileName")
|
||||
mainScope.launch {
|
||||
tmpFileContentToSave = data
|
||||
|
||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
type = "text/*"
|
||||
putExtra(Intent.EXTRA_TITLE, fileName)
|
||||
}.also {
|
||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
it?.data?.let { uri ->
|
||||
Log.d(TAG, "Save file to $uri")
|
||||
try {
|
||||
contentResolver.openOutputStream(uri)?.use { os ->
|
||||
os.bufferedWriter().use { it.write(data) }
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, "Failed to save file $uri: $e")
|
||||
// todo: send error to Qt
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -479,47 +404,42 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun openFile(filter: String?) {
|
||||
Log.v(TAG, "Open file with filter: $filter")
|
||||
mainScope.launch {
|
||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
extensionRegex.findAll(filter).map {
|
||||
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
||||
}.toSet()
|
||||
} else emptySet()
|
||||
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||
if ("*/*" in mimeTypes) {
|
||||
type = "*/*"
|
||||
} else {
|
||||
when (mimeTypes.size) {
|
||||
1 -> type = mimeTypes.first()
|
||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
||||
val extensionRegex = "\\*\\.([a-z0-9]+)".toRegex(IGNORE_CASE)
|
||||
val mime = MimeTypeMap.getSingleton()
|
||||
extensionRegex.findAll(filter).map {
|
||||
it.groups[1]?.value?.let { mime.getMimeTypeFromExtension(it) } ?: "*/*"
|
||||
}.toSet()
|
||||
} else emptySet()
|
||||
|
||||
in 2..Int.MAX_VALUE -> {
|
||||
type = "*/*"
|
||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||
}
|
||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||
addCategory(Intent.CATEGORY_OPENABLE)
|
||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||
if ("*/*" in mimeTypes) {
|
||||
type = "*/*"
|
||||
} else {
|
||||
when (mimeTypes.size) {
|
||||
1 -> type = mimeTypes.first()
|
||||
|
||||
else -> type = "*/*"
|
||||
in 2..Int.MAX_VALUE -> {
|
||||
type = "*/*"
|
||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||
}
|
||||
|
||||
else -> type = "*/*"
|
||||
}
|
||||
}.also {
|
||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE, ActivityResultHandler(
|
||||
onSuccess = {
|
||||
val uri = it?.data?.toString() ?: ""
|
||||
Log.d(TAG, "Open file: $uri")
|
||||
mainScope.launch {
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onFileOpened(uri)
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
}.also {
|
||||
startActivityForResult(it, OPEN_FILE_ACTION_CODE)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun setNotificationText(title: String, message: String, timerSec: Int) {
|
||||
Log.v(TAG, "Set notification text")
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||
|
||||
@@ -533,7 +453,7 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
@Suppress("unused")
|
||||
fun setSaveLogs(enabled: Boolean) {
|
||||
Log.v(TAG, "Set save logs: $enabled")
|
||||
Log.d(TAG, "Set save logs: $enabled")
|
||||
mainScope.launch {
|
||||
Log.saveLogs = enabled
|
||||
vpnServiceMessenger.send {
|
||||
@@ -553,9 +473,7 @@ class AmneziaActivity : QtActivity() {
|
||||
@Suppress("unused")
|
||||
fun clearLogs() {
|
||||
Log.v(TAG, "Clear logs")
|
||||
mainScope.launch {
|
||||
Log.clearLogs()
|
||||
}
|
||||
Log.clearLogs()
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
@@ -591,79 +509,7 @@ class AmneziaActivity : QtActivity() {
|
||||
|
||||
@Suppress("unused")
|
||||
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
|
||||
Log.v(TAG, "Get app icon")
|
||||
Log.v(TAG, "Get app icon: $packageName")
|
||||
return AppListProvider.getAppIcon(packageManager, packageName, width, height)
|
||||
}
|
||||
|
||||
@Suppress("unused")
|
||||
fun isNotificationPermissionGranted(): Boolean = applicationContext.isNotificationPermissionGranted()
|
||||
|
||||
@Suppress("unused")
|
||||
fun requestNotificationPermission() {
|
||||
val shouldShowPreRequest = shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
requestPermission(
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
CHECK_NOTIFICATION_PERMISSION_ACTION_CODE,
|
||||
PermissionRequestHandler(
|
||||
onSuccess = {
|
||||
mainScope.launch {
|
||||
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
|
||||
vpnServiceMessenger.send(Action.NOTIFICATION_PERMISSION_GRANTED)
|
||||
qtInitialized.await()
|
||||
QtAndroidController.onNotificationStateChanged()
|
||||
}
|
||||
},
|
||||
onFail = {
|
||||
if (!Prefs.load<Boolean>(PREFS_NOTIFICATION_PERMISSION_ASKED)) {
|
||||
Prefs.save(PREFS_NOTIFICATION_PERMISSION_ASKED, true)
|
||||
} else {
|
||||
val shouldShowPostRequest =
|
||||
shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
|
||||
if (!shouldShowPreRequest && !shouldShowPostRequest) {
|
||||
showNotificationSettingsDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun showNotificationSettingsDialog() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.notificationSettingsDialogTitle)
|
||||
.setMessage(R.string.notificationSettingsDialogMessage)
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> }
|
||||
.setPositiveButton(R.string.openNotificationSettings) { _, _ ->
|
||||
startActivity(Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS).apply {
|
||||
putExtra(Settings.EXTRA_APP_PACKAGE, packageName)
|
||||
})
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
/**
|
||||
* Utils methods
|
||||
*/
|
||||
companion object {
|
||||
private fun actionCodeToString(actionCode: Int): String =
|
||||
when (actionCode) {
|
||||
CHECK_VPN_PERMISSION_ACTION_CODE -> "CHECK_VPN_PERMISSION"
|
||||
CREATE_FILE_ACTION_CODE -> "CREATE_FILE"
|
||||
OPEN_FILE_ACTION_CODE -> "OPEN_FILE"
|
||||
CHECK_NOTIFICATION_PERMISSION_ACTION_CODE -> "CHECK_NOTIFICATION_PERMISSION"
|
||||
else -> actionCode.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ActivityResultHandler(
|
||||
val onSuccess: (data: Intent?) -> Unit = {},
|
||||
val onFail: (data: Intent?) -> Unit = {},
|
||||
val onAny: (data: Intent?) -> Unit = {}
|
||||
)
|
||||
|
||||
private class PermissionRequestHandler(
|
||||
val onSuccess: () -> Unit = {},
|
||||
val onFail: () -> Unit = {},
|
||||
val onAny: () -> Unit = {}
|
||||
)
|
||||
|
||||
@@ -3,11 +3,14 @@ package org.amnezia.vpn
|
||||
import androidx.camera.camera2.Camera2Config
|
||||
import androidx.camera.core.CameraSelector
|
||||
import androidx.camera.core.CameraXConfig
|
||||
import androidx.core.app.NotificationChannelCompat.Builder
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.qtproject.qt.android.bindings.QtApplication
|
||||
|
||||
private const val TAG = "AmneziaApplication"
|
||||
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
|
||||
|
||||
class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
||||
|
||||
@@ -17,7 +20,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
||||
Log.init(this)
|
||||
VpnStateStore.init(this)
|
||||
Log.d(TAG, "Create Amnezia application")
|
||||
ServiceNotification.createNotificationChannel(this)
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
override fun getCameraXConfig(): CameraXConfig = CameraXConfig.Builder
|
||||
@@ -25,4 +28,14 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
||||
.setMinimumLoggingLevel(android.util.Log.ERROR)
|
||||
.setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
|
||||
.build()
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
NotificationManagerCompat.from(this).createNotificationChannel(
|
||||
Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
|
||||
.setName("AmneziaVPN")
|
||||
.setDescription("AmneziaVPN service notification")
|
||||
.setShowBadge(false)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.ContextCompat.RegisterReceiverFlags
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.RECONNECTING
|
||||
import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN
|
||||
|
||||
fun Context.getString(state: ProtocolState): String =
|
||||
getString(
|
||||
when (state) {
|
||||
DISCONNECTED, UNKNOWN -> R.string.disconnected
|
||||
CONNECTED -> R.string.connected
|
||||
CONNECTING -> R.string.connecting
|
||||
DISCONNECTING -> R.string.disconnecting
|
||||
RECONNECTING -> R.string.reconnecting
|
||||
}
|
||||
)
|
||||
|
||||
fun Context.registerBroadcastReceiver(
|
||||
action: String,
|
||||
@RegisterReceiverFlags flags: Int = ContextCompat.RECEIVER_EXPORTED,
|
||||
onReceive: (Intent?) -> Unit
|
||||
): BroadcastReceiver = registerBroadcastReceiver(arrayOf(action), flags, onReceive)
|
||||
|
||||
fun Context.registerBroadcastReceiver(
|
||||
actions: Array<String>,
|
||||
@RegisterReceiverFlags flags: Int = ContextCompat.RECEIVER_EXPORTED,
|
||||
onReceive: (Intent?) -> Unit
|
||||
): BroadcastReceiver =
|
||||
object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) {
|
||||
onReceive(intent)
|
||||
}
|
||||
}.also {
|
||||
ContextCompat.registerReceiver(
|
||||
this,
|
||||
it,
|
||||
IntentFilter().apply {
|
||||
actions.forEach(::addAction)
|
||||
},
|
||||
flags
|
||||
)
|
||||
}
|
||||
|
||||
fun Context.unregisterBroadcastReceiver(receiver: BroadcastReceiver?) {
|
||||
receiver?.let { this.unregisterReceiver(it) }
|
||||
}
|
||||
@@ -188,16 +188,11 @@ class AmneziaTileService : TileService() {
|
||||
true
|
||||
}
|
||||
|
||||
private fun startVpnService() {
|
||||
try {
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, AmneziaVpnService::class.java)
|
||||
)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||
}
|
||||
}
|
||||
private fun startVpnService() =
|
||||
ContextCompat.startForegroundService(
|
||||
applicationContext,
|
||||
Intent(this, AmneziaVpnService::class.java)
|
||||
)
|
||||
|
||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||
|
||||
@@ -235,7 +230,7 @@ class AmneziaTileService : TileService() {
|
||||
val tile = qsTile ?: return
|
||||
tile.apply {
|
||||
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
||||
when (val protocolState = vpnState.protocolState) {
|
||||
when (vpnState.protocolState) {
|
||||
CONNECTED -> {
|
||||
state = Tile.STATE_ACTIVE
|
||||
subtitleCompat = null
|
||||
@@ -246,9 +241,14 @@ class AmneziaTileService : TileService() {
|
||||
subtitleCompat = null
|
||||
}
|
||||
|
||||
CONNECTING, DISCONNECTING, RECONNECTING -> {
|
||||
CONNECTING, RECONNECTING -> {
|
||||
state = Tile.STATE_UNAVAILABLE
|
||||
subtitleCompat = getString(protocolState)
|
||||
subtitleCompat = resources.getString(R.string.connecting)
|
||||
}
|
||||
|
||||
DISCONNECTING -> {
|
||||
state = Tile.STATE_UNAVAILABLE
|
||||
subtitleCompat = resources.getString(R.string.disconnecting)
|
||||
}
|
||||
}
|
||||
updateTile()
|
||||
|
||||
@@ -2,8 +2,8 @@ package org.amnezia.vpn
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||
import android.app.NotificationManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||
@@ -15,12 +15,10 @@ import android.os.IBinder
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.Messenger
|
||||
import android.os.PowerManager
|
||||
import android.os.Process
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.getSystemService
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||
@@ -56,14 +54,11 @@ import org.amnezia.vpn.protocol.wireguard.Wireguard
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.Prefs
|
||||
import org.amnezia.vpn.util.net.NetworkState
|
||||
import org.amnezia.vpn.util.net.TrafficStats
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
private const val TAG = "AmneziaVpnService"
|
||||
|
||||
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
|
||||
|
||||
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||
const val MSG_ERROR = "ERROR"
|
||||
const val MSG_SAVE_LOGS = "SAVE_LOGS"
|
||||
@@ -74,8 +69,8 @@ private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
||||
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
||||
// private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||
private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
|
||||
private const val NOTIFICATION_ID = 1337
|
||||
private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||
private const val DISCONNECT_TIMEOUT = 5000L
|
||||
private const val STOP_SERVICE_TIMEOUT = 5000L
|
||||
|
||||
@@ -101,14 +96,8 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
private var connectionJob: Job? = null
|
||||
private var disconnectionJob: Job? = null
|
||||
private var trafficStatsUpdateJob: Job? = null
|
||||
// private var statisticsSendingJob: Job? = null
|
||||
private var statisticsSendingJob: Job? = null
|
||||
private lateinit var networkState: NetworkState
|
||||
private lateinit var trafficStats: TrafficStats
|
||||
private var disconnectReceiver: BroadcastReceiver? = null
|
||||
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||
private var screenOnReceiver: BroadcastReceiver? = null
|
||||
private var screenOffReceiver: BroadcastReceiver? = null
|
||||
private val clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
|
||||
|
||||
private val isActivityConnected
|
||||
@@ -142,13 +131,13 @@ class AmneziaVpnService : VpnService() {
|
||||
val messenger = IpcMessenger(msg.replyTo, clientName)
|
||||
clientMessengers[msg.replyTo] = messenger
|
||||
Log.d(TAG, "Messenger client '$clientName' was registered")
|
||||
// if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
|
||||
if (clientName == ACTIVITY_MESSENGER_NAME && isConnected) launchSendingStatistics()
|
||||
}
|
||||
|
||||
Action.UNREGISTER_CLIENT -> {
|
||||
clientMessengers.remove(msg.replyTo)?.let {
|
||||
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
|
||||
// if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
|
||||
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,10 +159,6 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
}
|
||||
|
||||
Action.NOTIFICATION_PERMISSION_GRANTED -> {
|
||||
enableNotification()
|
||||
}
|
||||
|
||||
Action.SET_SAVE_LOGS -> {
|
||||
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
|
||||
}
|
||||
@@ -196,7 +181,25 @@ class AmneziaVpnService : VpnService() {
|
||||
else -> 0
|
||||
}
|
||||
|
||||
private val serviceNotification: ServiceNotification by lazy(NONE) { ServiceNotification(this) }
|
||||
private val notification: Notification by lazy(NONE) {
|
||||
NotificationCompat.Builder(this, NOTIFICATION_CHANNEL_ID)
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
.setShowWhen(false)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
Intent(this, AmneziaActivity::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.build()
|
||||
}
|
||||
|
||||
/**
|
||||
* Service overloaded methods
|
||||
@@ -209,8 +212,6 @@ class AmneziaVpnService : VpnService() {
|
||||
loadServerData()
|
||||
launchProtocolStateHandler()
|
||||
networkState = NetworkState(this, ::reconnect)
|
||||
trafficStats = TrafficStats()
|
||||
registerBroadcastReceivers()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
@@ -226,10 +227,7 @@ class AmneziaVpnService : VpnService() {
|
||||
Log.d(TAG, "Start service")
|
||||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||
}
|
||||
ServiceCompat.startForeground(
|
||||
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
|
||||
foregroundServiceTypeCompat
|
||||
)
|
||||
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
|
||||
return START_REDELIVER_INTENT
|
||||
}
|
||||
|
||||
@@ -269,7 +267,6 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
override fun onDestroy() {
|
||||
Log.d(TAG, "Destroy service")
|
||||
unregisterBroadcastReceivers()
|
||||
runBlocking {
|
||||
disconnect()
|
||||
disconnectionJob?.join()
|
||||
@@ -290,63 +287,6 @@ class AmneziaVpnService : VpnService() {
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
private fun registerBroadcastReceivers() {
|
||||
Log.d(TAG, "Register broadcast receivers")
|
||||
disconnectReceiver = registerBroadcastReceiver(ACTION_DISCONNECT, ContextCompat.RECEIVER_NOT_EXPORTED) {
|
||||
Log.d(TAG, "Broadcast request received: $ACTION_DISCONNECT")
|
||||
disconnect()
|
||||
}
|
||||
|
||||
notificationStateReceiver = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
registerBroadcastReceiver(
|
||||
arrayOf(
|
||||
NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
|
||||
NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED
|
||||
)
|
||||
) {
|
||||
val state = it?.getBooleanExtra(NotificationManager.EXTRA_BLOCKED_STATE, false)
|
||||
Log.d(TAG, "Notification state changed: ${it?.action}, blocked = $state")
|
||||
if (state == false) {
|
||||
enableNotification()
|
||||
} else {
|
||||
disableNotification()
|
||||
}
|
||||
}
|
||||
} else null
|
||||
|
||||
registerScreenStateBroadcastReceivers()
|
||||
}
|
||||
|
||||
private fun registerScreenStateBroadcastReceivers() {
|
||||
if (serviceNotification.isNotificationEnabled()) {
|
||||
Log.d(TAG, "Register screen state broadcast receivers")
|
||||
screenOnReceiver = registerBroadcastReceiver(Intent.ACTION_SCREEN_ON) {
|
||||
if (isConnected && serviceNotification.isNotificationEnabled()) startTrafficStatsUpdateJob()
|
||||
}
|
||||
|
||||
screenOffReceiver = registerBroadcastReceiver(Intent.ACTION_SCREEN_OFF) {
|
||||
stopTrafficStatsUpdateJob()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unregisterScreenStateBroadcastReceivers() {
|
||||
Log.d(TAG, "Unregister screen state broadcast receivers")
|
||||
unregisterBroadcastReceiver(screenOnReceiver)
|
||||
unregisterBroadcastReceiver(screenOffReceiver)
|
||||
screenOnReceiver = null
|
||||
screenOffReceiver = null
|
||||
}
|
||||
|
||||
private fun unregisterBroadcastReceivers() {
|
||||
Log.d(TAG, "Unregister broadcast receivers")
|
||||
unregisterBroadcastReceiver(disconnectReceiver)
|
||||
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||
unregisterScreenStateBroadcastReceivers()
|
||||
disconnectReceiver = null
|
||||
notificationStateReceiver = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Methods responsible for processing VPN connection
|
||||
*/
|
||||
@@ -355,8 +295,29 @@ class AmneziaVpnService : VpnService() {
|
||||
// drop first default UNKNOWN state
|
||||
protocolState.drop(1).collect { protocolState ->
|
||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||
when (protocolState) {
|
||||
CONNECTED -> {
|
||||
networkState.bindNetworkListener()
|
||||
if (isActivityConnected) launchSendingStatistics()
|
||||
}
|
||||
|
||||
serviceNotification.updateNotification(serverName, protocolState)
|
||||
DISCONNECTED -> {
|
||||
networkState.unbindNetworkListener()
|
||||
stopSendingStatistics()
|
||||
if (!isServiceBound) stopService()
|
||||
}
|
||||
|
||||
DISCONNECTING -> {
|
||||
networkState.unbindNetworkListener()
|
||||
stopSendingStatistics()
|
||||
}
|
||||
|
||||
RECONNECTING -> {
|
||||
stopSendingStatistics()
|
||||
}
|
||||
|
||||
CONNECTING, UNKNOWN -> {}
|
||||
}
|
||||
|
||||
clientMessengers.send {
|
||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||
@@ -365,41 +326,13 @@ class AmneziaVpnService : VpnService() {
|
||||
}
|
||||
|
||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
||||
|
||||
when (protocolState) {
|
||||
CONNECTED -> {
|
||||
networkState.bindNetworkListener()
|
||||
// if (isActivityConnected) launchSendingStatistics()
|
||||
launchTrafficStatsUpdate()
|
||||
}
|
||||
|
||||
DISCONNECTED -> {
|
||||
networkState.unbindNetworkListener()
|
||||
stopTrafficStatsUpdateJob()
|
||||
// stopSendingStatistics()
|
||||
if (!isServiceBound) stopService()
|
||||
}
|
||||
|
||||
DISCONNECTING -> {
|
||||
networkState.unbindNetworkListener()
|
||||
stopTrafficStatsUpdateJob()
|
||||
// stopSendingStatistics()
|
||||
}
|
||||
|
||||
RECONNECTING -> {
|
||||
stopTrafficStatsUpdateJob()
|
||||
// stopSendingStatistics()
|
||||
}
|
||||
|
||||
CONNECTING, UNKNOWN -> {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* @MainThread
|
||||
@MainThread
|
||||
private fun launchSendingStatistics() {
|
||||
if (isServiceBound && isConnected) {
|
||||
/* if (isServiceBound && isConnected) {
|
||||
statisticsSendingJob = mainScope.launch {
|
||||
while (true) {
|
||||
clientMessenger.send {
|
||||
@@ -410,62 +343,12 @@ class AmneziaVpnService : VpnService() {
|
||||
delay(STATISTICS_SENDING_TIMEOUT)
|
||||
}
|
||||
}
|
||||
}
|
||||
} */
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun stopSendingStatistics() {
|
||||
statisticsSendingJob?.cancel()
|
||||
} */
|
||||
|
||||
@MainThread
|
||||
private fun enableNotification() {
|
||||
registerScreenStateBroadcastReceivers()
|
||||
serviceNotification.updateNotification(serverName, protocolState.value)
|
||||
launchTrafficStatsUpdate()
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun disableNotification() {
|
||||
unregisterScreenStateBroadcastReceivers()
|
||||
stopTrafficStatsUpdateJob()
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun launchTrafficStatsUpdate() {
|
||||
stopTrafficStatsUpdateJob()
|
||||
if (isConnected &&
|
||||
serviceNotification.isNotificationEnabled() &&
|
||||
getSystemService<PowerManager>()?.isInteractive != false
|
||||
) {
|
||||
Log.d(TAG, "Launch traffic stats update")
|
||||
trafficStats.reset()
|
||||
startTrafficStatsUpdateJob()
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun startTrafficStatsUpdateJob() {
|
||||
if (trafficStatsUpdateJob == null && trafficStats.isSupported()) {
|
||||
Log.d(TAG, "Start traffic stats update")
|
||||
trafficStatsUpdateJob = mainScope.launch {
|
||||
while (true) {
|
||||
trafficStats.getSpeed().let { speed ->
|
||||
if (isConnected) {
|
||||
serviceNotification.updateSpeed(speed)
|
||||
}
|
||||
}
|
||||
delay(TRAFFIC_STATS_UPDATE_TIMEOUT)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@MainThread
|
||||
private fun stopTrafficStatsUpdateJob() {
|
||||
Log.d(TAG, "Stop traffic stats update")
|
||||
trafficStatsUpdateJob?.cancel()
|
||||
trafficStatsUpdateJob = null
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -588,7 +471,6 @@ class AmneziaVpnService : VpnService() {
|
||||
private fun saveServerData(config: JSONObject?) {
|
||||
serverName = config?.opt("description") as String?
|
||||
serverIndex = config?.opt("serverIndex") as Int? ?: -1
|
||||
Log.d(TAG, "Save server data: ($serverIndex, $serverName)")
|
||||
Prefs.save(PREFS_SERVER_NAME, serverName)
|
||||
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
|
||||
}
|
||||
@@ -596,7 +478,6 @@ class AmneziaVpnService : VpnService() {
|
||||
private fun loadServerData() {
|
||||
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
|
||||
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
|
||||
Log.d(TAG, "Load server data: ($serverIndex, $serverName)")
|
||||
}
|
||||
|
||||
private fun checkPermission(): Boolean =
|
||||
@@ -613,8 +494,9 @@ class AmneziaVpnService : VpnService() {
|
||||
|
||||
companion object {
|
||||
fun isRunning(context: Context): Boolean =
|
||||
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
}
|
||||
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
|
||||
.runningAppProcesses.any {
|
||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,6 @@ enum class Action : IpcMessage {
|
||||
CONNECT,
|
||||
DISCONNECT,
|
||||
REQUEST_STATUS,
|
||||
NOTIFICATION_PERMISSION_GRANTED,
|
||||
SET_SAVE_LOGS
|
||||
}
|
||||
|
||||
|
||||
189
client/android/src/org/amnezia/vpn/PackageManagerHelper.java
Normal file
189
client/android/src/org/amnezia/vpn/PackageManagerHelper.java
Normal file
@@ -0,0 +1,189 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package org.amnezia.vpn;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.Manifest.permission;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
import android.webkit.WebView;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
// Gets used by /platforms/android/androidAppListProvider.cpp
|
||||
public class PackageManagerHelper {
|
||||
final static String TAG = "PackageManagerHelper";
|
||||
final static int MIN_CHROME_VERSION = 65;
|
||||
|
||||
final static List<String> CHROME_BROWSERS = Arrays.asList(
|
||||
new String[] {"com.google.android.webview", "com.android.webview", "com.google.chrome"});
|
||||
|
||||
private static String getAllAppNames(Context ctx) {
|
||||
JSONObject output = new JSONObject();
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
List<String> browsers = getBrowserIDs(pm);
|
||||
List<PackageInfo> packs = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
||||
for (int i = 0; i < packs.size(); i++) {
|
||||
PackageInfo p = packs.get(i);
|
||||
// Do not add ourselves and System Apps to the list, unless it might be a browser
|
||||
if ((!isSystemPackage(p,pm) || browsers.contains(p.packageName))
|
||||
&& !isSelf(p)) {
|
||||
String appid = p.packageName;
|
||||
String appName = p.applicationInfo.loadLabel(pm).toString();
|
||||
try {
|
||||
output.put(appid, appName);
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
return output.toString();
|
||||
}
|
||||
|
||||
private static Drawable getAppIcon(Context ctx, String id) {
|
||||
try {
|
||||
return ctx.getPackageManager().getApplicationIcon(id);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return new ColorDrawable(Color.TRANSPARENT);
|
||||
}
|
||||
|
||||
private static boolean isSystemPackage(PackageInfo pkgInfo, PackageManager pm) {
|
||||
if( (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0){
|
||||
// no system app
|
||||
return false;
|
||||
}
|
||||
// For Systems Packages there are Cases where we want to add it anyway:
|
||||
// Has the use Internet permission (otherwise makes no sense)
|
||||
// Had at least 1 update (this means it's probably on any AppStore)
|
||||
// Has a a launch activity (has a ui and is not just a system service)
|
||||
|
||||
if(!usesInternet(pkgInfo)){
|
||||
return true;
|
||||
}
|
||||
if(!hadUpdate(pkgInfo)){
|
||||
return true;
|
||||
}
|
||||
if(pm.getLaunchIntentForPackage(pkgInfo.packageName) == null){
|
||||
// If there is no way to launch this from a homescreen, def a sys package
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static boolean isSelf(PackageInfo pkgInfo) {
|
||||
return pkgInfo.packageName.equals("org.amnezia.vpn")
|
||||
|| pkgInfo.packageName.equals("org.amnezia.vpn.debug");
|
||||
}
|
||||
private static boolean usesInternet(PackageInfo pkgInfo){
|
||||
if(pkgInfo.requestedPermissions == null){
|
||||
return false;
|
||||
}
|
||||
for(int i=0; i < pkgInfo.requestedPermissions.length; i++) {
|
||||
String permission = pkgInfo.requestedPermissions[i];
|
||||
if(Manifest.permission.INTERNET.equals(permission)){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static boolean hadUpdate(PackageInfo pkgInfo){
|
||||
return pkgInfo.lastUpdateTime > pkgInfo.firstInstallTime;
|
||||
}
|
||||
|
||||
// Returns List of all Packages that can classify themselves as browsers
|
||||
private static List<String> getBrowserIDs(PackageManager pm) {
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.amnezia.org/"));
|
||||
intent.addCategory(Intent.CATEGORY_BROWSABLE);
|
||||
// We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that
|
||||
// are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set
|
||||
// in the intent filter
|
||||
|
||||
List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL);
|
||||
List<String> browsers = new ArrayList<String>();
|
||||
for (int i = 0; i < resolveInfos.size(); i++) {
|
||||
ResolveInfo info = resolveInfos.get(i);
|
||||
String browserID = info.activityInfo.packageName;
|
||||
browsers.add(browserID);
|
||||
}
|
||||
return browsers;
|
||||
}
|
||||
|
||||
// Gets called in AndroidAuthenticationListener;
|
||||
public static boolean isWebViewSupported(Context ctx) {
|
||||
Log.v(TAG, "Checking if installed Webview is compatible with FxA");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// The default Webview is able do to FXA
|
||||
return true;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PackageInfo pi = WebView.getCurrentWebViewPackage();
|
||||
if (CHROME_BROWSERS.contains(pi.packageName)) {
|
||||
return isSupportedChromeBrowser(pi);
|
||||
}
|
||||
return isNotAncientBrowser(pi);
|
||||
}
|
||||
|
||||
// Before O the webview is hardcoded, but we dont know which package it is.
|
||||
// Check if com.google.android.webview is installed
|
||||
PackageManager pm = ctx.getPackageManager();
|
||||
try {
|
||||
PackageInfo pi = pm.getPackageInfo("com.google.android.webview", 0);
|
||||
return isSupportedChromeBrowser(pi);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
// Otherwise check com.android.webview
|
||||
try {
|
||||
PackageInfo pi = pm.getPackageInfo("com.android.webview", 0);
|
||||
return isSupportedChromeBrowser(pi);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
}
|
||||
Log.e(TAG, "Android System WebView is not found");
|
||||
// Giving up :(
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isSupportedChromeBrowser(PackageInfo pi) {
|
||||
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
|
||||
Log.d(TAG, "version name: " + pi.versionName);
|
||||
Log.d(TAG, "version code: " + pi.versionCode);
|
||||
try {
|
||||
String versionCode = pi.versionName.split(Pattern.quote(" "))[0];
|
||||
String majorVersion = versionCode.split(Pattern.quote("."))[0];
|
||||
int version = Integer.parseInt(majorVersion);
|
||||
return version >= MIN_CHROME_VERSION;
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to check Chrome Version Code " + pi.versionName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isNotAncientBrowser(PackageInfo pi) {
|
||||
// Not a google chrome - So the version name is worthless
|
||||
// Lets just make sure the WebView
|
||||
// used is not ancient ==> Was updated in at least the last 365 days
|
||||
Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName);
|
||||
Log.d(TAG, "version name: " + pi.versionName);
|
||||
Log.d(TAG, "version code: " + pi.versionCode);
|
||||
double oneYearInMillis = 31536000000L;
|
||||
return pi.lastUpdateTime > (System.currentTimeMillis() - oneYearInMillis);
|
||||
}
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package org.amnezia.vpn
|
||||
|
||||
import android.Manifest.permission
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.core.app.NotificationChannelCompat.Builder
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationCompat.Action
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import org.amnezia.vpn.protocol.ProtocolState
|
||||
import org.amnezia.vpn.protocol.ProtocolState.CONNECTED
|
||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||
import org.amnezia.vpn.util.Log
|
||||
import org.amnezia.vpn.util.net.TrafficStats.TrafficData
|
||||
|
||||
private const val TAG = "ServiceNotification"
|
||||
|
||||
private const val OLD_NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
|
||||
private const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notifications"
|
||||
const val NOTIFICATION_ID = 1337
|
||||
|
||||
private const val GET_ACTIVITY_REQUEST_CODE = 0
|
||||
private const val CONNECT_REQUEST_CODE = 1
|
||||
private const val DISCONNECT_REQUEST_CODE = 2
|
||||
|
||||
class ServiceNotification(private val context: Context) {
|
||||
|
||||
private val upDownSymbols = when (Build.BRAND) {
|
||||
"Infinix" -> '˅' to '˄'
|
||||
else -> '↓' to '↑'
|
||||
}
|
||||
|
||||
private val notificationManager = NotificationManagerCompat.from(context)
|
||||
|
||||
private val notificationBuilder = NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID)
|
||||
.setShowWhen(false)
|
||||
.setOngoing(true)
|
||||
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||
.setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)
|
||||
.setCategory(NotificationCompat.CATEGORY_SERVICE)
|
||||
.setContentIntent(
|
||||
PendingIntent.getActivity(
|
||||
context,
|
||||
GET_ACTIVITY_REQUEST_CODE,
|
||||
Intent(context, AmneziaActivity::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
|
||||
private val zeroSpeed: String = with(TrafficData.ZERO) {
|
||||
formatSpeedString(rxString, txString)
|
||||
}
|
||||
|
||||
fun buildNotification(serverName: String?, state: ProtocolState): Notification {
|
||||
val speedString = if (state == CONNECTED) zeroSpeed else null
|
||||
|
||||
Log.d(TAG, "Build notification: $serverName, $state")
|
||||
|
||||
return notificationBuilder
|
||||
.setSmallIcon(R.drawable.ic_amnezia_round)
|
||||
.setContentTitle(serverName ?: "AmneziaVPN")
|
||||
.setContentText(context.getString(state))
|
||||
.setSubText(speedString)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.clearActions()
|
||||
.apply {
|
||||
getAction(state)?.let {
|
||||
addAction(it)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
private fun buildNotification(speed: TrafficData): Notification =
|
||||
notificationBuilder
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setSubText(getSpeedString(speed))
|
||||
.build()
|
||||
|
||||
fun isNotificationEnabled(): Boolean {
|
||||
if (!context.isNotificationPermissionGranted()) return false
|
||||
if (!notificationManager.areNotificationsEnabled()) return false
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return notificationManager.getNotificationChannel(NOTIFICATION_CHANNEL_ID)
|
||||
?.let { it.importance != NotificationManager.IMPORTANCE_NONE } ?: true
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateNotification(serverName: String?, state: ProtocolState) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
Log.d(TAG, "Update notification: $serverName, $state")
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(serverName, state))
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun updateSpeed(speed: TrafficData) {
|
||||
if (context.isNotificationPermissionGranted()) {
|
||||
notificationManager.notify(NOTIFICATION_ID, buildNotification(speed))
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSpeedString(traffic: TrafficData) =
|
||||
if (traffic == TrafficData.ZERO) zeroSpeed
|
||||
else formatSpeedString(traffic.rxString, traffic.txString)
|
||||
|
||||
private fun formatSpeedString(rx: String, tx: String) = with(upDownSymbols) { "$first $rx $second $tx" }
|
||||
|
||||
private fun getAction(state: ProtocolState): Action? {
|
||||
return when (state) {
|
||||
CONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.disconnect),
|
||||
PendingIntent.getBroadcast(
|
||||
context,
|
||||
DISCONNECT_REQUEST_CODE,
|
||||
Intent(ACTION_DISCONNECT).apply {
|
||||
setPackage("org.amnezia.vpn")
|
||||
},
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
DISCONNECTED -> {
|
||||
Action(
|
||||
0, context.getString(R.string.connect),
|
||||
createServicePendingIntent(
|
||||
context,
|
||||
CONNECT_REQUEST_CODE,
|
||||
Intent(context, AmneziaVpnService::class.java),
|
||||
PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private val createServicePendingIntent: (Context, Int, Intent, Int) -> PendingIntent =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PendingIntent::getForegroundService
|
||||
} else {
|
||||
PendingIntent::getService
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun createNotificationChannel(context: Context) {
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
deleteNotificationChannel(OLD_NOTIFICATION_CHANNEL_ID)
|
||||
createNotificationChannel(
|
||||
Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_DEFAULT)
|
||||
.setShowBadge(false)
|
||||
.setSound(null, null)
|
||||
.setVibrationEnabled(false)
|
||||
.setLightsEnabled(false)
|
||||
.setName("AmneziaVPN")
|
||||
.setDescription(context.resources.getString(R.string.notificationChannelDescription))
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.isNotificationPermissionGranted(): Boolean =
|
||||
Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU ||
|
||||
ContextCompat.checkSelfPermission(this, permission.POST_NOTIFICATIONS) ==
|
||||
PackageManager.PERMISSION_GRANTED
|
||||
@@ -3,7 +3,9 @@ package org.amnezia.vpn
|
||||
import android.app.AlertDialog
|
||||
import android.app.KeyguardManager
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
|
||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||
import android.net.VpnService
|
||||
@@ -31,9 +33,11 @@ class VpnRequestActivity : ComponentActivity() {
|
||||
val requestIntent = VpnService.prepare(applicationContext)
|
||||
if (requestIntent != null) {
|
||||
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
||||
userPresentReceiver = registerBroadcastReceiver(Intent.ACTION_USER_PRESENT) {
|
||||
requestLauncher.launch(requestIntent)
|
||||
userPresentReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent?) =
|
||||
requestLauncher.launch(requestIntent)
|
||||
}
|
||||
registerReceiver(userPresentReceiver, IntentFilter(Intent.ACTION_USER_PRESENT))
|
||||
} else {
|
||||
requestLauncher.launch(requestIntent)
|
||||
}
|
||||
@@ -45,8 +49,9 @@ class VpnRequestActivity : ComponentActivity() {
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
unregisterBroadcastReceiver(userPresentReceiver)
|
||||
userPresentReceiver = null
|
||||
userPresentReceiver?.let {
|
||||
unregisterReceiver(it)
|
||||
}
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ import android.app.Application
|
||||
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||
import androidx.datastore.core.Serializer
|
||||
import androidx.datastore.dataStoreFile
|
||||
import java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.io.ObjectInputStream
|
||||
import java.io.ObjectOutputStream
|
||||
@@ -61,8 +59,7 @@ private class VpnStateSerializer : Serializer<VpnState> {
|
||||
|
||||
override suspend fun readFrom(input: InputStream): VpnState {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val bios = ByteArrayInputStream(input.readBytes())
|
||||
ObjectInputStream(bios).use {
|
||||
ObjectInputStream(input).use {
|
||||
it.readObject() as VpnState
|
||||
}
|
||||
}
|
||||
@@ -70,11 +67,9 @@ private class VpnStateSerializer : Serializer<VpnState> {
|
||||
|
||||
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
||||
withContext(Dispatchers.IO) {
|
||||
val baos = ByteArrayOutputStream()
|
||||
ObjectOutputStream(baos).use {
|
||||
ObjectOutputStream(output).use {
|
||||
it.writeObject(t)
|
||||
}
|
||||
output.write(baos.toByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ object QtAndroidController {
|
||||
external fun onServiceError()
|
||||
|
||||
external fun onVpnPermissionRejected()
|
||||
external fun onNotificationStateChanged()
|
||||
external fun onVpnStateChanged(stateCode: Int)
|
||||
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
||||
|
||||
|
||||
@@ -17,7 +17,5 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.androidx.security.crypto)
|
||||
}
|
||||
|
||||
@@ -109,11 +109,9 @@ object Log {
|
||||
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
|
||||
|
||||
fun clearLogs() {
|
||||
if (logDir.exists()) {
|
||||
withLock {
|
||||
logFile.delete()
|
||||
rotateLogFile.delete()
|
||||
}
|
||||
withLock {
|
||||
logFile.delete()
|
||||
rotateLogFile.delete()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
package org.amnezia.vpn.util.net
|
||||
|
||||
import java.net.Inet4Address
|
||||
import java.net.InetAddress
|
||||
|
||||
data class InetEndpoint(val address: InetAddress, val port: Int) {
|
||||
|
||||
override fun toString(): String = if (address is Inet4Address) {
|
||||
"${address.ip}:$port"
|
||||
} else {
|
||||
"[${address.ip}]:$port"
|
||||
}
|
||||
override fun toString(): String = "${address.hostAddress}:$port"
|
||||
|
||||
companion object {
|
||||
fun parse(data: String): InetEndpoint {
|
||||
val i = data.lastIndexOf(':')
|
||||
val address = parseInetAddress(data.substring(0, i))
|
||||
val port = data.substring(i + 1).toInt()
|
||||
val split = data.split(":")
|
||||
val address = parseInetAddress(split.first())
|
||||
val port = split.last().toInt()
|
||||
return InetEndpoint(address, port)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,7 @@ data class InetNetwork(val address: InetAddress, val mask: Int) {
|
||||
|
||||
constructor(address: InetAddress) : this(address, address.maxPrefixLength)
|
||||
|
||||
val isIpv4: Boolean = address is Inet4Address
|
||||
val isIpv6: Boolean
|
||||
get() = !isIpv4
|
||||
|
||||
override fun toString(): String = "${address.ip}/$mask"
|
||||
override fun toString(): String = "${address.hostAddress}/$mask"
|
||||
|
||||
companion object {
|
||||
fun parse(data: String): InetNetwork {
|
||||
|
||||
@@ -3,17 +3,12 @@ package org.amnezia.vpn.util.net
|
||||
import java.net.InetAddress
|
||||
|
||||
@OptIn(ExperimentalUnsignedTypes::class)
|
||||
internal class IpAddress private constructor(private val address: UByteArray) : Comparable<IpAddress> {
|
||||
class IpAddress private constructor(private val address: UByteArray) : Comparable<IpAddress> {
|
||||
|
||||
val size: Int = address.size
|
||||
val lastIndex: Int = address.lastIndex
|
||||
val maxMask: Int = size * 8
|
||||
|
||||
@OptIn(ExperimentalStdlibApi::class)
|
||||
val hexFormat: HexFormat by lazy {
|
||||
HexFormat { number.removeLeadingZeros = true }
|
||||
}
|
||||
|
||||
constructor(inetAddress: InetAddress) : this(inetAddress.address.asUByteArray())
|
||||
|
||||
constructor(ipAddress: String) : this(parseInetAddress(ipAddress))
|
||||
@@ -48,8 +43,6 @@ internal class IpAddress private constructor(private val address: UByteArray) :
|
||||
return copy
|
||||
}
|
||||
|
||||
fun isMinIp(): Boolean = address.all { it == 0x00u.toUByte() }
|
||||
|
||||
fun isMaxIp(): Boolean = address.all { it == 0xffu.toUByte() }
|
||||
|
||||
override fun compareTo(other: IpAddress): Int {
|
||||
@@ -81,14 +74,12 @@ internal class IpAddress private constructor(private val address: UByteArray) :
|
||||
private fun toIpv6String(): String {
|
||||
val sb = StringBuilder()
|
||||
var i = 0
|
||||
var block: Int
|
||||
while (i < size) {
|
||||
block = address[i++].toInt() shl 8
|
||||
block += address[i++].toInt()
|
||||
sb.append(block.toHexString(hexFormat))
|
||||
sb.append(address[i++].toHexString())
|
||||
sb.append(address[i++].toHexString())
|
||||
sb.append(':')
|
||||
}
|
||||
sb.deleteAt(sb.lastIndex)
|
||||
return convertIpv6ToCanonicalForm(sb.toString())
|
||||
return sb.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,14 @@ package org.amnezia.vpn.util.net
|
||||
|
||||
import java.net.InetAddress
|
||||
|
||||
class IpRange internal constructor(
|
||||
internal val start: IpAddress,
|
||||
internal val end: IpAddress
|
||||
) : Comparable<IpRange> {
|
||||
class IpRange(private val start: IpAddress, private val end: IpAddress) : Comparable<IpRange> {
|
||||
|
||||
init {
|
||||
if (start.size != end.size) {
|
||||
throw IllegalArgumentException(
|
||||
"Unable to create a range between IPv4 and IPv6 addresses (start IP: [$start], end IP: [$end])"
|
||||
)
|
||||
}
|
||||
if (start > end) throw IllegalArgumentException("Start IP: [$start] is greater then end IP: [$end]")
|
||||
if (start > end) throw IllegalArgumentException("Start IP: $start is greater then end IP: $end")
|
||||
}
|
||||
|
||||
private constructor(addresses: Pair<IpAddress, IpAddress>) : this(addresses.first, addresses.second)
|
||||
|
||||
internal constructor(ipAddress: IpAddress) : this(ipAddress, ipAddress)
|
||||
|
||||
constructor(inetAddress: InetAddress, mask: Int) : this(from(inetAddress, mask))
|
||||
|
||||
constructor(address: String, mask: Int) : this(parseInetAddress(address), mask)
|
||||
@@ -32,13 +22,6 @@ class IpRange internal constructor(
|
||||
private fun isIntersect(other: IpRange): Boolean =
|
||||
(start <= other.end) && (end >= other.start)
|
||||
|
||||
operator fun plus(other: IpRange): IpRange? {
|
||||
if (start > other.end && !start.isMinIp() && start.dec() == other.end) return IpRange(other.start, end)
|
||||
if (end < other.start && !end.isMaxIp() && end.inc() == other.start) return IpRange(start, other.end)
|
||||
if (!isIntersect(other)) return null
|
||||
return IpRange(minOf(start, other.start), maxOf(end, other.end))
|
||||
}
|
||||
|
||||
operator fun minus(other: IpRange): List<IpRange>? {
|
||||
if (this in other) return emptyList()
|
||||
if (!isIntersect(other)) return null
|
||||
@@ -111,7 +94,9 @@ class IpRange internal constructor(
|
||||
return result
|
||||
}
|
||||
|
||||
override fun toString(): String = if (start == end) "<$start>" else "<$start - $end>"
|
||||
override fun toString(): String {
|
||||
return "$start - $end"
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun from(inetAddress: InetAddress, mask: Int): Pair<IpAddress, IpAddress> {
|
||||
|
||||
@@ -1,35 +1,15 @@
|
||||
package org.amnezia.vpn.util.net
|
||||
|
||||
class IpRangeSet {
|
||||
class IpRangeSet(ipRange: IpRange = IpRange("0.0.0.0", 0)) {
|
||||
|
||||
private val ranges = sortedSetOf<IpRange>()
|
||||
|
||||
fun add(ipRange: IpRange) {
|
||||
val iterator = ranges.iterator()
|
||||
var rangeToAdd = ipRange
|
||||
run {
|
||||
while (iterator.hasNext()) {
|
||||
val curRange = iterator.next()
|
||||
if (rangeToAdd.end < curRange.start &&
|
||||
!rangeToAdd.end.isMaxIp() &&
|
||||
rangeToAdd.end.inc() != curRange.start) break
|
||||
(curRange + rangeToAdd)?.let { resultRange ->
|
||||
if (resultRange == curRange) return@run
|
||||
iterator.remove()
|
||||
rangeToAdd = resultRange
|
||||
}
|
||||
}
|
||||
ranges += rangeToAdd
|
||||
}
|
||||
}
|
||||
private val ranges = sortedSetOf(ipRange)
|
||||
|
||||
fun remove(ipRange: IpRange) {
|
||||
val iterator = ranges.iterator()
|
||||
val splitRanges = mutableListOf<IpRange>()
|
||||
while (iterator.hasNext()) {
|
||||
val curRange = iterator.next()
|
||||
if (ipRange.end < curRange.start) break
|
||||
(curRange - ipRange)?.let { resultRanges ->
|
||||
val range = iterator.next()
|
||||
(range - ipRange)?.let { resultRanges ->
|
||||
iterator.remove()
|
||||
splitRanges += resultRanges
|
||||
}
|
||||
@@ -37,7 +17,10 @@ class IpRangeSet {
|
||||
ranges += splitRanges
|
||||
}
|
||||
|
||||
fun subnets(): List<InetNetwork> = ranges.map(IpRange::subnets).flatten()
|
||||
fun subnets(): List<InetNetwork> =
|
||||
ranges.map(IpRange::subnets).flatten()
|
||||
|
||||
override fun toString(): String = ranges.toString()
|
||||
override fun toString(): String {
|
||||
return ranges.toString()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,7 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
||||
import android.net.NetworkRequest
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import androidx.core.content.getSystemService
|
||||
import kotlin.LazyThreadSafetyMode.NONE
|
||||
import kotlinx.coroutines.delay
|
||||
import org.amnezia.vpn.util.Log
|
||||
|
||||
private const val TAG = "NetworkState"
|
||||
@@ -30,7 +28,7 @@ class NetworkState(
|
||||
}
|
||||
|
||||
private val connectivityManager: ConnectivityManager by lazy(NONE) {
|
||||
context.getSystemService<ConnectivityManager>()!!
|
||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
}
|
||||
|
||||
private val networkRequest: NetworkRequest by lazy(NONE) {
|
||||
@@ -82,24 +80,13 @@ class NetworkState(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun bindNetworkListener() {
|
||||
fun bindNetworkListener() {
|
||||
if (isListenerBound) return
|
||||
Log.d(TAG, "Bind network listener")
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
try {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
} catch (e: SecurityException) {
|
||||
Log.e(TAG, "Failed to bind network listener: $e")
|
||||
// Android 11 bug: https://issuetracker.google.com/issues/175055271
|
||||
if (e.message?.startsWith("Package android does not belong to") == true) {
|
||||
delay(1000)
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
||||
} else {
|
||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||
}
|
||||
|
||||
@@ -5,14 +5,12 @@ import android.net.ConnectivityManager
|
||||
import android.net.InetAddresses
|
||||
import android.net.NetworkCapabilities
|
||||
import android.os.Build
|
||||
import androidx.core.content.getSystemService
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.net.Inet4Address
|
||||
import java.net.Inet6Address
|
||||
import java.net.InetAddress
|
||||
|
||||
fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
|
||||
val connectivityManager = context.getSystemService<ConnectivityManager>()!!
|
||||
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
|
||||
connectivityManager.activeNetwork?.let { network ->
|
||||
val netCapabilities = connectivityManager.getNetworkCapabilities(network)
|
||||
val linkProperties = connectivityManager.getLinkProperties(network)
|
||||
@@ -41,28 +39,8 @@ private val parseNumericAddressCompat: (String) -> InetAddress =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
InetAddresses::parseNumericAddress
|
||||
} else {
|
||||
try {
|
||||
val m = InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
|
||||
fun(address: String): InetAddress {
|
||||
try {
|
||||
return m.invoke(null, address) as InetAddress
|
||||
} catch (e: InvocationTargetException) {
|
||||
throw e.cause ?: e
|
||||
}
|
||||
}
|
||||
} catch (_: NoSuchMethodException) {
|
||||
fun(address: String): InetAddress {
|
||||
return InetAddress.getByName(address)
|
||||
}
|
||||
val m = InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
|
||||
fun(address: String): InetAddress {
|
||||
return m.invoke(null, address) as InetAddress
|
||||
}
|
||||
}
|
||||
|
||||
internal fun convertIpv6ToCanonicalForm(ipv6: String): String = ipv6
|
||||
.replace("((?:(?:^|:)0+\\b){2,}):?(?!\\S*\\b\\1:0+\\b)(\\S*)".toRegex(), "::$2")
|
||||
|
||||
internal val InetAddress.ip: String
|
||||
get() = if (this is Inet4Address) {
|
||||
hostAddress!!
|
||||
} else {
|
||||
convertIpv6ToCanonicalForm(hostAddress!!)
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package org.amnezia.vpn.util.net
|
||||
|
||||
import android.net.TrafficStats
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import android.os.SystemClock
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
private const val BYTE = 1L
|
||||
private const val KiB = BYTE shl 10
|
||||
private const val MiB = KiB shl 10
|
||||
private const val GiB = MiB shl 10
|
||||
private const val TiB = GiB shl 10
|
||||
|
||||
class TrafficStats {
|
||||
|
||||
private var lastTrafficData = TrafficData.ZERO
|
||||
private var lastTimestamp = 0L
|
||||
|
||||
private val getTrafficDataCompat: () -> TrafficData =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
val iface = "tun0"
|
||||
fun(): TrafficData {
|
||||
return TrafficData(TrafficStats.getRxBytes(iface), TrafficStats.getTxBytes(iface))
|
||||
}
|
||||
} else {
|
||||
val uid = Process.myUid()
|
||||
fun(): TrafficData {
|
||||
return TrafficData(TrafficStats.getUidRxBytes(uid), TrafficStats.getUidTxBytes(uid))
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
lastTrafficData = getTrafficDataCompat()
|
||||
lastTimestamp = SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
fun isSupported(): Boolean =
|
||||
lastTrafficData.rx != TrafficStats.UNSUPPORTED.toLong() && lastTrafficData.tx != TrafficStats.UNSUPPORTED.toLong()
|
||||
|
||||
fun getSpeed(): TrafficData {
|
||||
val timestamp = SystemClock.elapsedRealtime()
|
||||
val elapsedSeconds = (timestamp - lastTimestamp) / 1000.0
|
||||
val trafficData = getTrafficDataCompat()
|
||||
val speed = trafficData.diff(lastTrafficData, elapsedSeconds)
|
||||
lastTrafficData = trafficData
|
||||
lastTimestamp = timestamp
|
||||
return speed
|
||||
}
|
||||
|
||||
class TrafficData(val rx: Long, val tx: Long) {
|
||||
|
||||
private var _rxString: String? = null
|
||||
val rxString: String
|
||||
get() {
|
||||
if (_rxString == null) _rxString = rx.speedToString()
|
||||
return _rxString ?: throw AssertionError("Set to null by another thread")
|
||||
}
|
||||
|
||||
private var _txString: String? = null
|
||||
val txString: String
|
||||
get() {
|
||||
if (_txString == null) _txString = tx.speedToString()
|
||||
return _txString ?: throw AssertionError("Set to null by another thread")
|
||||
}
|
||||
|
||||
fun diff(other: TrafficData, elapsedSeconds: Double): TrafficData {
|
||||
val rx = ((this.rx - other.rx) / elapsedSeconds).round()
|
||||
val tx = ((this.tx - other.tx) / elapsedSeconds).round()
|
||||
return if (rx == 0L && tx == 0L) ZERO else TrafficData(rx, tx)
|
||||
}
|
||||
|
||||
private fun Double.round() = if (isNaN()) 0L else roundToLong()
|
||||
|
||||
private fun Long.speedToString() =
|
||||
when {
|
||||
this < KiB -> formatSize(this, BYTE, "B/s")
|
||||
this < MiB -> formatSize(this, KiB, "KiB/s")
|
||||
this < GiB -> formatSize(this, MiB, "MiB/s")
|
||||
this < TiB -> formatSize(this, GiB, "GiB/s")
|
||||
else -> formatSize(this, TiB, "TiB/s")
|
||||
}
|
||||
|
||||
private fun formatSize(bytes: Long, divider: Long, unit: String): String {
|
||||
val s = (bytes.toDouble() / divider * 100).roundToLong() / 100.0
|
||||
return "${s.toString().removeSuffix(".0")} $unit"
|
||||
}
|
||||
|
||||
companion object {
|
||||
val ZERO: TrafficData = TrafficData(0L, 0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,8 +37,8 @@ open class WireguardConfig protected constructor(
|
||||
|
||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||
appendLine("public_key=$publicKeyHex")
|
||||
routes.filter { it.include }.forEach { route ->
|
||||
appendLine("allowed_ip=${route.inetNetwork}")
|
||||
routes.forEach { route ->
|
||||
appendLine("allowed_ip=$route")
|
||||
}
|
||||
appendLine("endpoint=$endpoint")
|
||||
if (persistentKeepalive != 0)
|
||||
|
||||
@@ -26,6 +26,7 @@ link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||
|
||||
set(HEADERS ${HEADERS}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||
@@ -34,6 +35,7 @@ set(HEADERS ${HEADERS}
|
||||
|
||||
set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_notificationhandler.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_utils.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||
|
||||
@@ -46,6 +46,7 @@ set(SOURCES ${SOURCES}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QShar
|
||||
}
|
||||
|
||||
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ public:
|
||||
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||
};
|
||||
|
||||
#endif // AWGCONFIGURATOR_H
|
||||
|
||||
@@ -13,7 +13,7 @@ CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const Q
|
||||
}
|
||||
|
||||
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
QString cloakPublicKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
|
||||
|
||||
@@ -14,7 +14,7 @@ public:
|
||||
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||
};
|
||||
|
||||
#endif // CLOAK_CONFIGURATOR_H
|
||||
|
||||
@@ -15,7 +15,7 @@ public:
|
||||
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
virtual QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode) = 0;
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode) = 0;
|
||||
|
||||
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
|
||||
@@ -20,7 +20,7 @@ Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const Q
|
||||
}
|
||||
|
||||
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
Ikev2Configurator::ConnectionData connData;
|
||||
connData.host = credentials.hostName;
|
||||
@@ -55,7 +55,7 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
||||
}
|
||||
|
||||
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
Q_UNUSED(containerConfig)
|
||||
|
||||
|
||||
@@ -22,14 +22,14 @@ public:
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||
|
||||
QString genIkev2Config(const ConnectionData &connData);
|
||||
QString genMobileConfig(const ConnectionData &connData);
|
||||
QString genStrongSwanConfig(const ConnectionData &connData);
|
||||
|
||||
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
|
||||
DockerContainer container, ErrorCode &errorCode);
|
||||
DockerContainer container, ErrorCode errorCode);
|
||||
};
|
||||
|
||||
#endif // IKEV2_CONFIGURATOR_H
|
||||
|
||||
@@ -31,7 +31,7 @@ OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, con
|
||||
}
|
||||
|
||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container, ErrorCode &errorCode)
|
||||
DockerContainer container, ErrorCode errorCode)
|
||||
{
|
||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||
connData.host = credentials.hostName;
|
||||
@@ -72,7 +72,7 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(co
|
||||
}
|
||||
|
||||
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||
{
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
@@ -116,8 +116,8 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString,
|
||||
if (!isApiConfig) {
|
||||
QRegularExpression regex("redirect-gateway.*");
|
||||
config.replace(regex, "");
|
||||
|
||||
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||
|
||||
if (!m_settings->getSitesSplitTunnelingEnabled()) {
|
||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||
// Prevent ipv6 leak
|
||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||
QString &protocolConfigString);
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
|
||||
private:
|
||||
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
ErrorCode &errorCode);
|
||||
ErrorCode errorCode);
|
||||
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
|
||||
};
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> setti
|
||||
}
|
||||
|
||||
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||
{
|
||||
QString ssKey =
|
||||
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
|
||||
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||
};
|
||||
|
||||
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
||||
|
||||
@@ -65,7 +65,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
||||
|
||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||
DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||
{
|
||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||
connData.host = credentials.hostName;
|
||||
@@ -158,7 +158,7 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
||||
}
|
||||
|
||||
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode)
|
||||
{
|
||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||
QString config =
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
};
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
ErrorCode errorCode);
|
||||
|
||||
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
|
||||
private:
|
||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, ErrorCode errorCode);
|
||||
|
||||
bool m_isAwg;
|
||||
QString m_serverConfigPath;
|
||||
|
||||
@@ -14,7 +14,7 @@ XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSh
|
||||
}
|
||||
|
||||
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||
|
||||
@@ -13,7 +13,7 @@ public:
|
||||
XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||
|
||||
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||
ErrorCode &errorCode);
|
||||
ErrorCode errorCode);
|
||||
};
|
||||
|
||||
#endif // XRAY_CONFIGURATOR_H
|
||||
|
||||
@@ -321,7 +321,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return true;
|
||||
case DockerContainer::Awg: return true;
|
||||
// case DockerContainer::Cloak: return true;
|
||||
case DockerContainer::Cloak: return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
@@ -330,8 +330,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
||||
{
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("Low");
|
||||
case DockerContainer::Awg: return tr("High");
|
||||
// case DockerContainer::Cloak: return tr("Extreme");
|
||||
case DockerContainer::Awg: return tr("Medium or High");
|
||||
case DockerContainer::Cloak: return tr("Extreme");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -341,8 +341,8 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
||||
case DockerContainer::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
||||
// case DockerContainer::Cloak:
|
||||
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
||||
case DockerContainer::Cloak:
|
||||
return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
@@ -352,7 +352,7 @@ int ContainerProps::easySetupOrder(DockerContainer container)
|
||||
switch (container) {
|
||||
case DockerContainer::WireGuard: return 3;
|
||||
case DockerContainer::Awg: return 2;
|
||||
// case DockerContainer::Cloak: return 1;
|
||||
case DockerContainer::Cloak: return 1;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
#include <QNetworkReply>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#include "amnezia_application.h"
|
||||
#include "configurators/wireguard_configurator.h"
|
||||
#include "version.h"
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -21,10 +19,7 @@ namespace
|
||||
constexpr char certificate[] = "certificate";
|
||||
constexpr char publicKey[] = "public_key";
|
||||
constexpr char protocol[] = "protocol";
|
||||
|
||||
constexpr char uuid[] = "installation_uuid";
|
||||
constexpr char osVersion[] = "os_version";
|
||||
constexpr char appVersion[] = "app_version";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +27,8 @@ ApiController::ApiController(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
||||
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData,
|
||||
QString &config)
|
||||
{
|
||||
if (protocol == configKey::cloak) {
|
||||
config.replace("<key>", "<key>\n");
|
||||
@@ -65,52 +61,53 @@ QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiCont
|
||||
} else if (protocol == configKey::awg) {
|
||||
obj[configKey::publicKey] = apiPayloadData.wireGuardClientPubKey;
|
||||
}
|
||||
|
||||
obj[configKey::osVersion] = QSysInfo::productType();
|
||||
obj[configKey::appVersion] = QString(APP_VERSION);
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
void ApiController::updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig)
|
||||
ErrorCode ApiController::updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig)
|
||||
{
|
||||
#ifdef Q_OS_IOS
|
||||
IosController::Instance()->requestInetAccess();
|
||||
QThread::msleep(10);
|
||||
#endif
|
||||
QFutureWatcher<ErrorCode> watcher;
|
||||
|
||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
||||
QFuture<ErrorCode> future = QtConcurrent::run([this, &serverConfig, &installationUuid]() {
|
||||
auto containerConfig = serverConfig.value(config_key::containers).toArray();
|
||||
|
||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization", "Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||
request.setUrl(endpoint);
|
||||
if (serverConfig.value(config_key::configVersion).toInt()) {
|
||||
QNetworkAccessManager manager;
|
||||
|
||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||
QNetworkRequest request;
|
||||
request.setTransferTimeout(7000);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
request.setRawHeader("Authorization",
|
||||
"Api-Key " + serverConfig.value(configKey::accessToken).toString().toUtf8());
|
||||
QString endpoint = serverConfig.value(configKey::apiEdnpoint).toString();
|
||||
request.setUrl(endpoint);
|
||||
|
||||
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||
|
||||
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
auto apiPayloadData = generateApiPayloadData(protocol);
|
||||
|
||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||
auto apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||
apiPayload[configKey::uuid] = installationUuid;
|
||||
|
||||
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
||||
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||
|
||||
QScopedPointer<QNetworkReply> reply;
|
||||
reply.reset(manager.post(request, requestBody));
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(reply.get(), &QNetworkReply::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, [this, reply, protocol, apiPayloadData, serverIndex, serverConfig]() mutable {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
QString contents = QString::fromUtf8(reply->readAll());
|
||||
QString data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
||||
auto data = QJsonDocument::fromJson(contents.toUtf8()).object().value(config_key::config).toString();
|
||||
|
||||
data.replace("vpn://", "");
|
||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
QByteArray ba = QByteArray::fromBase64(data.toUtf8(),
|
||||
QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||
|
||||
if (ba.isEmpty()) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||
return;
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
|
||||
QByteArray ba_uncompressed = qUncompress(ba);
|
||||
@@ -122,37 +119,30 @@ void ApiController::updateServerConfigFromApi(const QString &installationUuid, c
|
||||
processApiConfig(protocol, apiPayloadData, configStr);
|
||||
|
||||
QJsonObject apiConfig = QJsonDocument::fromJson(configStr.toUtf8()).object();
|
||||
serverConfig[config_key::dns1] = apiConfig.value(config_key::dns1);
|
||||
serverConfig[config_key::dns2] = apiConfig.value(config_key::dns2);
|
||||
serverConfig[config_key::containers] = apiConfig.value(config_key::containers);
|
||||
serverConfig[config_key::hostName] = apiConfig.value(config_key::hostName);
|
||||
|
||||
serverConfig.insert(config_key::dns1, apiConfig.value(config_key::dns1));
|
||||
serverConfig.insert(config_key::dns2, apiConfig.value(config_key::dns2));
|
||||
serverConfig.insert(config_key::containers, apiConfig.value(config_key::containers));
|
||||
serverConfig.insert(config_key::hostName, apiConfig.value(config_key::hostName));
|
||||
|
||||
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||
|
||||
emit configUpdated(true, serverConfig, serverIndex);
|
||||
serverConfig.insert(config_key::defaultContainer, defaultContainer);
|
||||
} else {
|
||||
if (reply->error() == QNetworkReply::NetworkError::OperationCanceledError
|
||||
|| reply->error() == QNetworkReply::NetworkError::TimeoutError) {
|
||||
emit errorOccurred(ErrorCode::ApiConfigTimeoutError);
|
||||
} else {
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
emit errorOccurred(ErrorCode::ApiConfigDownloadError);
|
||||
}
|
||||
QString err = reply->errorString();
|
||||
qDebug() << QString::fromUtf8(reply->readAll());
|
||||
qDebug() << reply->error();
|
||||
qDebug() << err;
|
||||
qDebug() << reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
|
||||
return ErrorCode::ApiConfigDownloadError;
|
||||
}
|
||||
}
|
||||
return ErrorCode::NoError;
|
||||
});
|
||||
|
||||
reply->deleteLater();
|
||||
});
|
||||
QEventLoop wait;
|
||||
connect(&watcher, &QFutureWatcher<ErrorCode>::finished, &wait, &QEventLoop::quit);
|
||||
watcher.setFuture(future);
|
||||
wait.exec();
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::errorOccurred,
|
||||
[this, reply](QNetworkReply::NetworkError error) { qDebug() << reply->errorString() << error; });
|
||||
connect(reply, &QNetworkReply::sslErrors, [this, reply](const QList<QSslError> &errors) {
|
||||
qDebug().noquote() << errors;
|
||||
emit errorOccurred(ErrorCode::ApiConfigSslError);
|
||||
});
|
||||
}
|
||||
return watcher.result();
|
||||
}
|
||||
|
||||
@@ -5,10 +5,6 @@
|
||||
|
||||
#include "configurators/openvpn_configurator.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "platforms/ios/ios_controller.h"
|
||||
#endif
|
||||
|
||||
class ApiController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -17,15 +13,10 @@ public:
|
||||
explicit ApiController(QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||
|
||||
signals:
|
||||
void errorOccurred(ErrorCode errorCode);
|
||||
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
||||
ErrorCode updateServerConfigFromApi(const QString &installationUuid, QJsonObject &serverConfig);
|
||||
|
||||
private:
|
||||
struct ApiPayloadData
|
||||
{
|
||||
struct ApiPayloadData {
|
||||
OpenVpnConfigurator::ConnectionData certRequest;
|
||||
|
||||
QString wireGuardClientPrivKey;
|
||||
|
||||
@@ -173,7 +173,7 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
||||
}
|
||||
|
||||
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
|
||||
errorCode = ErrorCode::NoError;
|
||||
@@ -618,7 +618,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
||||
return vars;
|
||||
}
|
||||
|
||||
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode)
|
||||
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode)
|
||||
{
|
||||
QString stdOut;
|
||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||
|
||||
@@ -36,7 +36,7 @@ public:
|
||||
const QString &path,
|
||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||
ErrorCode &errorCode);
|
||||
ErrorCode errorCode);
|
||||
|
||||
QString replaceVars(const QString &script, const Vars &vars);
|
||||
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||
|
||||
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode);
|
||||
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode errorCode);
|
||||
|
||||
void cancelInstallation();
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isA
|
||||
|
||||
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||
const QJsonObject &containerConfig, const DockerContainer container,
|
||||
ErrorCode &errorCode)
|
||||
ErrorCode errorCode)
|
||||
{
|
||||
QJsonObject vpnConfiguration {};
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public slots:
|
||||
const DockerContainer container, const QJsonObject &containerConfig, const Proto protocol,
|
||||
QString &protocolConfigString);
|
||||
QJsonObject createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||
const QJsonObject &containerConfig, const DockerContainer container, ErrorCode &errorCode);
|
||||
const QJsonObject &containerConfig, const DockerContainer container, ErrorCode errorCode);
|
||||
|
||||
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
|
||||
signals:
|
||||
|
||||
@@ -36,17 +36,12 @@ namespace amnezia
|
||||
}
|
||||
};
|
||||
|
||||
namespace error_code_ns
|
||||
{
|
||||
Q_NAMESPACE
|
||||
// TODO: change to enum class
|
||||
enum ErrorCode {
|
||||
enum ErrorCode {
|
||||
// General error codes
|
||||
NoError = 0,
|
||||
UnknownError = 100,
|
||||
InternalError = 101,
|
||||
NotImplementedError = 102,
|
||||
AmneziaServiceNotRunning = 103,
|
||||
|
||||
// Server errors
|
||||
ServerCheckFailed = 200,
|
||||
@@ -79,7 +74,7 @@ namespace amnezia
|
||||
AmneziaServiceConnectionFailed = 603,
|
||||
ExecutableMissing = 604,
|
||||
XrayExecutableMissing = 605,
|
||||
Tun2SockExecutableMissing = 606,
|
||||
Tun2SockExecutableMissing = 606,
|
||||
|
||||
// VPN errors
|
||||
OpenVpnAdaptersInUseError = 700,
|
||||
@@ -103,9 +98,6 @@ namespace amnezia
|
||||
// Api errors
|
||||
ApiConfigDownloadError = 1100,
|
||||
ApiConfigAlreadyAdded = 1101,
|
||||
ApiConfigEmptyError = 1102,
|
||||
ApiConfigTimeoutError = 1103,
|
||||
ApiConfigSslError = 1104,
|
||||
|
||||
// QFile errors
|
||||
OpenError = 1200,
|
||||
@@ -114,11 +106,7 @@ namespace amnezia
|
||||
UnspecifiedError = 1203,
|
||||
FatalError = 1204,
|
||||
AbortError = 1205
|
||||
};
|
||||
Q_ENUM_NS(ErrorCode)
|
||||
}
|
||||
|
||||
using ErrorCode = error_code_ns::ErrorCode;
|
||||
};
|
||||
|
||||
} // namespace amnezia
|
||||
|
||||
|
||||
@@ -8,68 +8,64 @@ QString errorString(ErrorCode code) {
|
||||
switch (code) {
|
||||
|
||||
// General error codes
|
||||
case(ErrorCode::NoError): errorMessage = QObject::tr("No error"); break;
|
||||
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
||||
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
||||
case(NoError): errorMessage = QObject::tr("No error"); break;
|
||||
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
||||
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||
|
||||
// Server errors
|
||||
case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
||||
case(ErrorCode::ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
|
||||
case(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||
case(ErrorCode::ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||
case(ErrorCode::ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
||||
case(ErrorCode::ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
||||
case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
|
||||
case(ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
||||
case(ServerUserNotInSudo): errorMessage = QObject::tr("The user does not have permission to use sudo"); break;
|
||||
case(ServerPacketManagerError): errorMessage = QObject::tr("Server error: Packet manager error"); break;
|
||||
|
||||
// Libssh errors
|
||||
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
||||
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
||||
case(ErrorCode::SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
||||
case(ErrorCode::SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
||||
case(ErrorCode::SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||
case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
||||
case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
||||
case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
||||
case(SshPrivateKeyFormatError): errorMessage = QObject::tr("The selected private key format is not supported, use openssh ED25519 key types or PEM key types"); break;
|
||||
case(SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||
|
||||
// Ssh scp errors
|
||||
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
||||
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
||||
|
||||
// Local errors
|
||||
case (ErrorCode::OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||
case (ErrorCode::OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
|
||||
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||
case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
|
||||
|
||||
// Distro errors
|
||||
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
||||
case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
||||
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||
case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||
case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
||||
case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
||||
case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||
case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||
|
||||
// VPN errors
|
||||
case (ErrorCode::OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
|
||||
case (ErrorCode::OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
|
||||
case (ErrorCode::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
||||
case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
|
||||
case (OpenVpnTapAdapterError): errorMessage = QObject::tr("Can't setup OpenVPN TAP network adapter"); break;
|
||||
case (AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
||||
|
||||
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||
case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||
|
||||
// Android errors
|
||||
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||
case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||
|
||||
// Api errors
|
||||
case (ErrorCode::ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||
case (ErrorCode::ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
||||
case (ErrorCode::ApiConfigEmptyError): errorMessage = QObject::tr("In the response from the server, an empty config was received"); break;
|
||||
case (ErrorCode::ApiConfigSslError): errorMessage = QObject::tr("SSL error occurred"); break;
|
||||
case (ErrorCode::ApiConfigTimeoutError): errorMessage = QObject::tr("Server response timeout on api request"); break;
|
||||
|
||||
// QFile errors
|
||||
case(ErrorCode::OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
case(ErrorCode::ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
||||
case(ErrorCode::PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
|
||||
case(ErrorCode::UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
||||
case(ErrorCode::FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
||||
case(ErrorCode::AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
||||
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
||||
case (ApiConfigAlreadyAdded): errorMessage = QObject::tr("This config has already been added to the application"); break;
|
||||
|
||||
case(ErrorCode::InternalError):
|
||||
// QFile errors
|
||||
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
||||
case(ReadError): errorMessage = QObject::tr("QFile error: An error occurred when reading from the file"); break;
|
||||
case(PermissionsError): errorMessage = QObject::tr("QFile error: The file could not be accessed"); break;
|
||||
case(UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
||||
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
||||
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
||||
|
||||
case(InternalError):
|
||||
default:
|
||||
errorMessage = QObject::tr("Internal error"); break;
|
||||
}
|
||||
|
||||
@@ -248,10 +248,9 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
|
||||
GETVALUE("privateKey", config.m_privateKey, String);
|
||||
GETVALUE("serverPublicKey", config.m_serverPublicKey, String);
|
||||
GETVALUE("serverPskKey", config.m_serverPskKey, String);
|
||||
GETVALUE("serverPort", config.m_serverPort, Double);
|
||||
|
||||
config.m_serverPskKey = obj.value("serverPskKey").toString();
|
||||
|
||||
if (!obj.contains("deviceMTU") || obj.value("deviceMTU").toString().toInt() == 0)
|
||||
{
|
||||
config.m_deviceMTU = 1420;
|
||||
@@ -374,8 +373,6 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
|
||||
return false;
|
||||
}
|
||||
|
||||
config.m_killSwitchEnabled = QVariant(obj.value("killSwitchOption").toString()).toBool();
|
||||
|
||||
if (!obj.value("Jc").isNull()) {
|
||||
config.m_junkPacketCount = obj.value("Jc").toString();
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ class InterfaceConfig {
|
||||
QList<IPAddress> m_allowedIPAddressRanges;
|
||||
QStringList m_excludedAddresses;
|
||||
QStringList m_vpnDisabledApps;
|
||||
bool m_killSwitchEnabled;
|
||||
#if defined(MZ_ANDROID) || defined(MZ_IOS)
|
||||
QString m_installationId;
|
||||
#endif
|
||||
|
||||
@@ -51,13 +51,6 @@
|
||||
<true/>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>Amnezia VPN needs access to the camera for reading QR-codes.</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<false/>
|
||||
<key>NSAllowsLocalNetworking</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>CFBundleIcons</key>
|
||||
<dict/>
|
||||
<key>CFBundleIcons~ipad</key>
|
||||
|
||||
@@ -64,7 +64,6 @@ int main(int argc, char *argv[])
|
||||
|
||||
qInfo().noquote() << QString("Started %1 version %2 %3").arg(APPLICATION_NAME, APP_VERSION, GIT_COMMIT_HASH);
|
||||
qInfo().noquote() << QString("%1 (%2)").arg(QSysInfo::prettyProductName(), QSysInfo::currentCpuArchitecture());
|
||||
qInfo().noquote() << QString("SSL backend: %1").arg(QSslSocket::sslLibraryVersionString());
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -221,9 +221,7 @@ void LocalSocketController::activate(const QJsonObject &rawConfig) {
|
||||
json.insert("excludedAddresses", jsExcludedAddresses);
|
||||
|
||||
json.insert("vpnDisabledApps", splitTunnelApps);
|
||||
|
||||
json.insert(amnezia::config_key::killSwitchOption, rawConfig.value(amnezia::config_key::killSwitchOption));
|
||||
|
||||
|
||||
if (protocolName == amnezia::config_key::awg) {
|
||||
json.insert(amnezia::config_key::junkPacketCount, wgConfig.value(amnezia::config_key::junkPacketCount));
|
||||
json.insert(amnezia::config_key::junkPacketMinSize, wgConfig.value(amnezia::config_key::junkPacketMinSize));
|
||||
|
||||
@@ -171,15 +171,9 @@ void NetworkWatcher::unsecuredNetwork(const QString& networkName,
|
||||
}
|
||||
|
||||
|
||||
QNetworkInformation::Reachability NetworkWatcher::getReachability() {
|
||||
if (m_simulatedDisconnection) {
|
||||
return QNetworkInformation::Reachability::Disconnected;
|
||||
} else if (QNetworkInformation::instance()) {
|
||||
return QNetworkInformation::instance()->reachability();
|
||||
}
|
||||
return QNetworkInformation::Reachability::Unknown;
|
||||
}
|
||||
|
||||
void NetworkWatcher::simulateDisconnection(bool simulatedDisconnection) {
|
||||
m_simulatedDisconnection = simulatedDisconnection;
|
||||
QString NetworkWatcher::getCurrentTransport() {
|
||||
auto type = m_impl->getTransportType();
|
||||
QMetaEnum metaEnum = QMetaEnum::fromType<NetworkWatcherImpl::TransportType>();
|
||||
return QString(metaEnum.valueToKey(type))
|
||||
.remove("TransportType_", Qt::CaseSensitive);
|
||||
}
|
||||
|
||||
@@ -7,50 +7,45 @@
|
||||
|
||||
#include <QElapsedTimer>
|
||||
#include <QMap>
|
||||
#include <QNetworkInformation>
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class NetworkWatcherImpl;
|
||||
|
||||
// This class watches for network changes to detect unsecured wifi.
|
||||
class NetworkWatcher final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(NetworkWatcher)
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(NetworkWatcher)
|
||||
|
||||
public:
|
||||
NetworkWatcher();
|
||||
~NetworkWatcher();
|
||||
public:
|
||||
NetworkWatcher();
|
||||
~NetworkWatcher();
|
||||
|
||||
void initialize();
|
||||
void initialize();
|
||||
|
||||
// Public for the Inspector.
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
// Used for the Inspector. simulateOffline = true to mock being disconnected,
|
||||
// false to restore.
|
||||
void simulateDisconnection(bool simulatedDisconnection);
|
||||
// public for the inspector.
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
|
||||
QNetworkInformation::Reachability getReachability();
|
||||
QString getCurrentTransport();
|
||||
|
||||
signals:
|
||||
void networkChange();
|
||||
signals:
|
||||
void networkChange();
|
||||
|
||||
private:
|
||||
void settingsChanged();
|
||||
private:
|
||||
void settingsChanged();
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
bool m_reportUnsecuredNetwork = false;
|
||||
// void notificationClicked(NotificationHandler::Message message);
|
||||
|
||||
// Platform-specific implementation.
|
||||
NetworkWatcherImpl* m_impl = nullptr;
|
||||
private:
|
||||
bool m_active = false;
|
||||
bool m_reportUnsecuredNetwork = false;
|
||||
|
||||
QMap<QString, QElapsedTimer> m_networks;
|
||||
// Platform-specific implementation.
|
||||
NetworkWatcherImpl* m_impl = nullptr;
|
||||
|
||||
// This is used to connect NotificationHandler lazily.
|
||||
bool m_firstNotification = true;
|
||||
QMap<QString, QElapsedTimer> m_networks;
|
||||
|
||||
// Used to simulate network disconnection in the Inspector
|
||||
bool m_simulatedDisconnection = false;
|
||||
// This is used to connect NotificationHandler lazily.
|
||||
bool m_firstNotification = true;
|
||||
};
|
||||
|
||||
#endif // NETWORKWATCHER_H
|
||||
|
||||
@@ -5,45 +5,50 @@
|
||||
#ifndef NETWORKWATCHERIMPL_H
|
||||
#define NETWORKWATCHERIMPL_H
|
||||
|
||||
#include <QNetworkInformation>
|
||||
#include <QObject>
|
||||
|
||||
class NetworkWatcherImpl : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(NetworkWatcherImpl)
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(NetworkWatcherImpl)
|
||||
|
||||
public:
|
||||
NetworkWatcherImpl(QObject* parent) : QObject(parent) {}
|
||||
public:
|
||||
NetworkWatcherImpl(QObject* parent) : QObject(parent) {}
|
||||
|
||||
virtual ~NetworkWatcherImpl() = default;
|
||||
virtual ~NetworkWatcherImpl() = default;
|
||||
|
||||
virtual void initialize() = 0;
|
||||
virtual void initialize() = 0;
|
||||
|
||||
virtual void start() { m_active = true; }
|
||||
virtual void stop() { m_active = false; }
|
||||
virtual void start() { m_active = true; }
|
||||
virtual void stop() { m_active = false; }
|
||||
|
||||
bool isActive() const { return m_active; }
|
||||
bool isActive() const { return m_active; }
|
||||
|
||||
enum TransportType {
|
||||
TransportType_Unknown = 0,
|
||||
TransportType_Ethernet = 1,
|
||||
TransportType_WiFi = 2,
|
||||
TransportType_Cellular = 3, // In Case the API does not retun the gsm type
|
||||
TransportType_Other = 4, // I.e USB thethering
|
||||
TransportType_None = 5 // I.e Airplane Mode or no active network device
|
||||
};
|
||||
Q_ENUM(TransportType);
|
||||
enum TransportType {
|
||||
TransportType_Unknown = 0,
|
||||
TransportType_Ethernet = 1,
|
||||
TransportType_WiFi = 2,
|
||||
TransportType_Cellular = 3, // In Case the API does not retun the gsm type
|
||||
TransportType_Other = 4, // I.e USB thethering
|
||||
TransportType_None = 5 // I.e Airplane Mode or no active network device
|
||||
};
|
||||
Q_ENUM(TransportType);
|
||||
|
||||
signals:
|
||||
// Fires when the Device Connects to an unsecured Network
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
// Fires on when the connected WIFI Changes
|
||||
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
||||
// too.
|
||||
void networkChanged(QString newBSSID);
|
||||
// Returns the current type of Network Connection
|
||||
virtual TransportType getTransportType() = 0;
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
signals:
|
||||
// Fires when the Device Connects to an unsecured Network
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
// Fires on when the connected WIFI Changes
|
||||
// TODO: Only windows-networkwatcher has this, the other plattforms should
|
||||
// too.
|
||||
void networkChanged(QString newBSSID);
|
||||
|
||||
// Fired when the Device changed the Type of Transport
|
||||
void transportChanged(NetworkWatcherImpl::TransportType transportType);
|
||||
|
||||
private:
|
||||
bool m_active = false;
|
||||
};
|
||||
|
||||
#endif // NETWORKWATCHERIMPL_H
|
||||
|
||||
@@ -93,7 +93,6 @@ bool AndroidController::initialize()
|
||||
{"onServiceDisconnected", "()V", reinterpret_cast<void *>(onServiceDisconnected)},
|
||||
{"onServiceError", "()V", reinterpret_cast<void *>(onServiceError)},
|
||||
{"onVpnPermissionRejected", "()V", reinterpret_cast<void *>(onVpnPermissionRejected)},
|
||||
{"onNotificationStateChanged", "()V", reinterpret_cast<void *>(onNotificationStateChanged)},
|
||||
{"onVpnStateChanged", "(I)V", reinterpret_cast<void *>(onVpnStateChanged)},
|
||||
{"onStatisticsUpdate", "(JJ)V", reinterpret_cast<void *>(onStatisticsUpdate)},
|
||||
{"onFileOpened", "(Ljava/lang/String;)V", reinterpret_cast<void *>(onFileOpened)},
|
||||
@@ -136,7 +135,7 @@ ErrorCode AndroidController::start(const QJsonObject &vpnConfig)
|
||||
callActivityMethod("start", "(Ljava/lang/String;)V",
|
||||
QJniObject::fromString(config).object<jstring>());
|
||||
|
||||
return ErrorCode::NoError;
|
||||
return NoError;
|
||||
}
|
||||
|
||||
void AndroidController::stop()
|
||||
@@ -174,6 +173,14 @@ QString AndroidController::openFile(const QString &filter)
|
||||
return fileName;
|
||||
}
|
||||
|
||||
void AndroidController::setNotificationText(const QString &title, const QString &message, int timerSec)
|
||||
{
|
||||
callActivityMethod("setNotificationText", "(Ljava/lang/String;Ljava/lang/String;I)V",
|
||||
QJniObject::fromString(title).object<jstring>(),
|
||||
QJniObject::fromString(message).object<jstring>(),
|
||||
(jint) timerSec);
|
||||
}
|
||||
|
||||
bool AndroidController::isCameraPresent()
|
||||
{
|
||||
return callActivityMethod<jboolean>("isCameraPresent", "()Z");
|
||||
@@ -250,16 +257,6 @@ QPixmap AndroidController::getAppIcon(const QString &package, QSize *size, const
|
||||
return QPixmap::fromImage(image);
|
||||
}
|
||||
|
||||
bool AndroidController::isNotificationPermissionGranted()
|
||||
{
|
||||
return callActivityMethod<jboolean>("isNotificationPermissionGranted", "()Z");
|
||||
}
|
||||
|
||||
void AndroidController::requestNotificationPermission()
|
||||
{
|
||||
callActivityMethod("requestNotificationPermission", "()V");
|
||||
}
|
||||
|
||||
// Moving log processing to the Android side
|
||||
jclass AndroidController::log;
|
||||
jmethodID AndroidController::logDebug;
|
||||
@@ -412,15 +409,6 @@ void AndroidController::onVpnPermissionRejected(JNIEnv *env, jobject thiz)
|
||||
emit AndroidController::instance()->vpnPermissionRejected();
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidController::onNotificationStateChanged(JNIEnv *env, jobject thiz)
|
||||
{
|
||||
Q_UNUSED(env);
|
||||
Q_UNUSED(thiz);
|
||||
|
||||
emit AndroidController::instance()->notificationStateChanged();
|
||||
}
|
||||
|
||||
// static
|
||||
void AndroidController::onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode)
|
||||
{
|
||||
|
||||
@@ -32,6 +32,7 @@ public:
|
||||
ErrorCode start(const QJsonObject &vpnConfig);
|
||||
void stop();
|
||||
void resetLastServer(int serverIndex);
|
||||
void setNotificationText(const QString &title, const QString &message, int timerSec);
|
||||
void saveFile(const QString &fileName, const QString &data);
|
||||
QString openFile(const QString &filter);
|
||||
bool isCameraPresent();
|
||||
@@ -43,8 +44,6 @@ public:
|
||||
void minimizeApp();
|
||||
QJsonArray getAppList();
|
||||
QPixmap getAppIcon(const QString &package, QSize *size, const QSize &requestedSize);
|
||||
bool isNotificationPermissionGranted();
|
||||
void requestNotificationPermission();
|
||||
|
||||
static bool initLogging();
|
||||
static void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message);
|
||||
@@ -55,7 +54,6 @@ signals:
|
||||
void serviceDisconnected();
|
||||
void serviceError();
|
||||
void vpnPermissionRejected();
|
||||
void notificationStateChanged();
|
||||
void vpnStateChanged(ConnectionState state);
|
||||
void statisticsUpdated(quint64 rxBytes, quint64 txBytes);
|
||||
void fileOpened(QString uri);
|
||||
@@ -83,7 +81,6 @@ private:
|
||||
static void onServiceDisconnected(JNIEnv *env, jobject thiz);
|
||||
static void onServiceError(JNIEnv *env, jobject thiz);
|
||||
static void onVpnPermissionRejected(JNIEnv *env, jobject thiz);
|
||||
static void onNotificationStateChanged(JNIEnv *env, jobject thiz);
|
||||
static void onVpnStateChanged(JNIEnv *env, jobject thiz, jint stateCode);
|
||||
static void onStatisticsUpdate(JNIEnv *env, jobject thiz, jlong rxBytes, jlong txBytes);
|
||||
static void onConfigImported(JNIEnv *env, jobject thiz, jstring data);
|
||||
|
||||
21
client/platforms/android/android_notificationhandler.cpp
Normal file
21
client/platforms/android/android_notificationhandler.cpp
Normal file
@@ -0,0 +1,21 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "android_notificationhandler.h"
|
||||
#include "platforms/android/android_controller.h"
|
||||
|
||||
AndroidNotificationHandler::AndroidNotificationHandler(QObject* parent)
|
||||
: NotificationHandler(parent) {
|
||||
}
|
||||
AndroidNotificationHandler::~AndroidNotificationHandler() {
|
||||
}
|
||||
|
||||
void AndroidNotificationHandler::notify(NotificationHandler::Message type,
|
||||
const QString& title,
|
||||
const QString& message, int timerMsec) {
|
||||
Q_UNUSED(type);
|
||||
qDebug() << "Send notification - " << message;
|
||||
AndroidController::instance()->setNotificationText(title, message,
|
||||
timerMsec / 1000);
|
||||
}
|
||||
24
client/platforms/android/android_notificationhandler.h
Normal file
24
client/platforms/android/android_notificationhandler.h
Normal file
@@ -0,0 +1,24 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef ANDROIDNOTIFICATIONHANDLER_H
|
||||
#define ANDROIDNOTIFICATIONHANDLER_H
|
||||
|
||||
#include "ui/notificationhandler.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class AndroidNotificationHandler final : public NotificationHandler {
|
||||
Q_DISABLE_COPY_MOVE(AndroidNotificationHandler)
|
||||
|
||||
public:
|
||||
AndroidNotificationHandler(QObject* parent);
|
||||
~AndroidNotificationHandler();
|
||||
|
||||
protected:
|
||||
void notify(Message type, const QString& title, const QString& message,
|
||||
int timerMsec) override;
|
||||
};
|
||||
|
||||
#endif // ANDROIDNOTIFICATIONHANDLER_H
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
DummyNetworkWatcher::DummyNetworkWatcher(QObject* parent)
|
||||
: NetworkWatcherImpl(parent) {
|
||||
MZ_COUNT_CTOR(DummyNetworkWatcher);
|
||||
MZ_COUNT_CTOR(DummyNetworkWatcher);
|
||||
}
|
||||
|
||||
DummyNetworkWatcher::~DummyNetworkWatcher() {
|
||||
MZ_COUNT_DTOR(DummyNetworkWatcher);
|
||||
MZ_COUNT_DTOR(DummyNetworkWatcher);
|
||||
}
|
||||
|
||||
void DummyNetworkWatcher::initialize() {}
|
||||
|
||||
@@ -8,11 +8,15 @@
|
||||
#include "networkwatcherimpl.h"
|
||||
|
||||
class DummyNetworkWatcher final : public NetworkWatcherImpl {
|
||||
public:
|
||||
DummyNetworkWatcher(QObject* parent);
|
||||
~DummyNetworkWatcher();
|
||||
public:
|
||||
DummyNetworkWatcher(QObject* parent);
|
||||
~DummyNetworkWatcher();
|
||||
|
||||
void initialize() override;
|
||||
void initialize() override;
|
||||
|
||||
NetworkWatcherImpl::TransportType getTransportType() override {
|
||||
return TransportType_Other;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // DUMMYNETWORKWATCHER_H
|
||||
|
||||
15
client/platforms/ios/MobileUtils.cpp
Normal file
15
client/platforms/ios/MobileUtils.cpp
Normal file
@@ -0,0 +1,15 @@
|
||||
#include "MobileUtils.h"
|
||||
|
||||
MobileUtils::MobileUtils(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool MobileUtils::shareText(const QStringList &)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString MobileUtils::openFile()
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
22
client/platforms/ios/MobileUtils.h
Normal file
22
client/platforms/ios/MobileUtils.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#ifndef MOBILEUTILS_H
|
||||
#define MOBILEUTILS_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
class MobileUtils : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MobileUtils(QObject *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
bool shareText(const QStringList &filesToSend);
|
||||
QString openFile();
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
};
|
||||
|
||||
#endif // MOBILEUTILS_H
|
||||
109
client/platforms/ios/MobileUtils.mm
Normal file
109
client/platforms/ios/MobileUtils.mm
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "MobileUtils.h"
|
||||
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <Security/Security.h>
|
||||
|
||||
#include <QEventLoop>
|
||||
|
||||
static UIViewController* getViewController() {
|
||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window.isKeyWindow) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
MobileUtils::MobileUtils(QObject *parent) : QObject(parent) {
|
||||
|
||||
}
|
||||
|
||||
bool MobileUtils::shareText(const QStringList& filesToSend) {
|
||||
NSMutableArray *sharingItems = [NSMutableArray new];
|
||||
|
||||
for (int i = 0; i < filesToSend.size(); i++) {
|
||||
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
||||
[sharingItems addObject:logFileUrl];
|
||||
}
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
|
||||
__block bool isAccepted = false;
|
||||
|
||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
isAccepted = completed;
|
||||
emit finished();
|
||||
}];
|
||||
|
||||
[qtController presentViewController:activityController animated:YES completion:nil];
|
||||
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
||||
if (popController) {
|
||||
popController.sourceView = qtController.view;
|
||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
}
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
return isAccepted;
|
||||
}
|
||||
|
||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
|
||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||
|
||||
@end
|
||||
|
||||
@implementation DocumentPickerDelegate
|
||||
|
||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
if (self.documentPickerClosedCallback) {
|
||||
self.documentPickerClosedCallback([url path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
||||
if (self.documentPickerClosedCallback) {
|
||||
self.documentPickerClosedCallback(nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
QString MobileUtils::openFile() {
|
||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||
|
||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||
documentPicker.delegate = documentPickerDelegate;
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
|
||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
|
||||
__block QString filePath;
|
||||
|
||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
if (path) {
|
||||
filePath = QString::fromUtf8(path.UTF8String);
|
||||
} else {
|
||||
filePath = QString();
|
||||
}
|
||||
emit finished();
|
||||
};
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &MobileUtils::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
return filePath;
|
||||
}
|
||||
@@ -50,19 +50,12 @@ public:
|
||||
|
||||
void getBackendLogs(std::function<void(const QString &)> &&callback);
|
||||
void checkStatus();
|
||||
|
||||
bool shareText(const QStringList &filesToSend);
|
||||
QString openFile();
|
||||
|
||||
void requestInetAccess();
|
||||
signals:
|
||||
void connectionStateChanged(Vpn::ConnectionState state);
|
||||
void bytesChanged(quint64 receivedBytes, quint64 sentBytes);
|
||||
void importConfigFromOutside(const QString);
|
||||
void importBackupFromOutside(const QString);
|
||||
|
||||
void finished();
|
||||
|
||||
protected slots:
|
||||
|
||||
private:
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QThread>
|
||||
#include <QEventLoop>
|
||||
|
||||
#include "../protocols/vpnprotocol.h"
|
||||
#import "ios_controller_wrapper.h"
|
||||
@@ -27,15 +26,6 @@ const char* MessageKey::isOnDemand = "is-on-demand";
|
||||
const char* MessageKey::SplitTunnelType = "SplitTunnelType";
|
||||
const char* MessageKey::SplitTunnelSites = "SplitTunnelSites";
|
||||
|
||||
static UIViewController* getViewController() {
|
||||
NSArray *windows = [[UIApplication sharedApplication]windows];
|
||||
for (UIWindow *window in windows) {
|
||||
if (window.isKeyWindow) {
|
||||
return window.rootViewController;
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
Vpn::ConnectionState iosStatusToState(NEVPNStatus status) {
|
||||
switch (status) {
|
||||
@@ -713,86 +703,3 @@ void IosController::sendVpnExtensionMessage(NSDictionary* message, std::function
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool IosController::shareText(const QStringList& filesToSend) {
|
||||
NSMutableArray *sharingItems = [NSMutableArray new];
|
||||
|
||||
for (int i = 0; i < filesToSend.size(); i++) {
|
||||
NSURL *logFileUrl = [[NSURL alloc] initFileURLWithPath:filesToSend[i].toNSString()];
|
||||
[sharingItems addObject:logFileUrl];
|
||||
}
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
|
||||
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:sharingItems applicationActivities:nil];
|
||||
|
||||
__block bool isAccepted = false;
|
||||
|
||||
[activityController setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError) {
|
||||
isAccepted = completed;
|
||||
emit finished();
|
||||
}];
|
||||
|
||||
[qtController presentViewController:activityController animated:YES completion:nil];
|
||||
UIPopoverPresentationController *popController = activityController.popoverPresentationController;
|
||||
if (popController) {
|
||||
popController.sourceView = qtController.view;
|
||||
popController.sourceRect = CGRectMake(100, 100, 100, 100);
|
||||
}
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
return isAccepted;
|
||||
}
|
||||
|
||||
QString IosController::openFile() {
|
||||
UIDocumentPickerViewController *documentPicker = [[UIDocumentPickerViewController alloc] initWithDocumentTypes:@[@"public.item"] inMode:UIDocumentPickerModeOpen];
|
||||
|
||||
DocumentPickerDelegate *documentPickerDelegate = [[DocumentPickerDelegate alloc] init];
|
||||
documentPicker.delegate = documentPickerDelegate;
|
||||
|
||||
UIViewController *qtController = getViewController();
|
||||
if (!qtController) return;
|
||||
|
||||
[qtController presentViewController:documentPicker animated:YES completion:nil];
|
||||
|
||||
__block QString filePath;
|
||||
|
||||
documentPickerDelegate.documentPickerClosedCallback = ^(NSString *path) {
|
||||
if (path) {
|
||||
filePath = QString::fromUtf8(path.UTF8String);
|
||||
} else {
|
||||
filePath = QString();
|
||||
}
|
||||
emit finished();
|
||||
};
|
||||
|
||||
QEventLoop wait;
|
||||
QObject::connect(this, &IosController::finished, &wait, &QEventLoop::quit);
|
||||
wait.exec();
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
void IosController::requestInetAccess() {
|
||||
NSURL *url = [NSURL URLWithString:@"http://captive.apple.com/generate_204"];
|
||||
if (url) {
|
||||
qDebug() << "IosController::requestInetAccess URL error";
|
||||
return;
|
||||
}
|
||||
|
||||
NSURLSession *session = [NSURLSession sharedSession];
|
||||
NSURLSessionDataTask *task = [session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
if (error) {
|
||||
qDebug() << "IosController::requestInetAccess error:" << error.localizedDescription;
|
||||
} else {
|
||||
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
||||
QString responseBody = QString::fromUtf8((const char*)data.bytes, data.length);
|
||||
qDebug() << "IosController::requestInetAccess server response:" << httpResponse.statusCode << "\n\n" <<responseBody;
|
||||
}
|
||||
}];
|
||||
[task resume];
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#import <NetworkExtension/NetworkExtension.h>
|
||||
#import <NetworkExtension/NETunnelProviderSession.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#include <UIKit/UIKit.h>
|
||||
#include <Security/Security.h>
|
||||
|
||||
class IosController;
|
||||
|
||||
@@ -15,11 +13,3 @@ class IosController;
|
||||
- (void)vpnConfigurationDidChange:(NSNotification *)notification;
|
||||
|
||||
@end
|
||||
|
||||
typedef void (^DocumentPickerClosedCallback)(NSString *path);
|
||||
|
||||
@interface DocumentPickerDelegate : NSObject <UIDocumentPickerDelegate>
|
||||
|
||||
@property (nonatomic, copy) DocumentPickerClosedCallback documentPickerClosedCallback;
|
||||
|
||||
@end
|
||||
|
||||
@@ -24,22 +24,5 @@
|
||||
// cppController->vpnStatusDidChange(notification);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@implementation DocumentPickerDelegate
|
||||
|
||||
- (void)documentPicker:(UIDocumentPickerViewController *)controller didPickDocumentsAtURLs:(NSArray<NSURL *> *)urls {
|
||||
for (NSURL *url in urls) {
|
||||
if (self.documentPickerClosedCallback) {
|
||||
self.documentPickerClosedCallback([url path]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (void)documentPickerWasCancelled:(UIDocumentPickerViewController *)controller {
|
||||
if (self.documentPickerClosedCallback) {
|
||||
self.documentPickerClosedCallback(nil);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -15,6 +15,7 @@ class IOSNetworkWatcher : public NetworkWatcherImpl {
|
||||
~IOSNetworkWatcher();
|
||||
|
||||
void initialize() override;
|
||||
NetworkWatcherImpl::TransportType getTransportType() override;
|
||||
|
||||
private:
|
||||
NetworkWatcherImpl::TransportType toTransportType(nw_path_t path);
|
||||
|
||||
@@ -37,6 +37,16 @@ void IOSNetworkWatcher::initialize() {
|
||||
//TODO IMPL FOR AMNEZIA
|
||||
}
|
||||
|
||||
NetworkWatcherImpl::TransportType IOSNetworkWatcher::getTransportType() {
|
||||
//TODO IMPL FOR AMNEZIA
|
||||
|
||||
if (m_observableConnection != nil) {
|
||||
return m_currentVPNTransport;
|
||||
}
|
||||
// If we don't have an open tunnel-observer, m_currentVPNTransport is probably wrong.
|
||||
return NetworkWatcherImpl::TransportType_Unknown;
|
||||
}
|
||||
|
||||
NetworkWatcherImpl::TransportType IOSNetworkWatcher::toTransportType(nw_path_t path) {
|
||||
if (path == nil) {
|
||||
return NetworkWatcherImpl::TransportType_Unknown;
|
||||
|
||||
@@ -136,25 +136,24 @@ bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
}
|
||||
applyFirewallRules(params);
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
}
|
||||
}
|
||||
|
||||
applyFirewallRules(params);
|
||||
}
|
||||
|
||||
return (err == 0);
|
||||
@@ -200,9 +199,7 @@ bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||
if (!config.m_serverPskKey.isNull()) {
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||
|
||||
5
client/platforms/linux/linuxnetworkwatcher.h
Executable file → Normal file
5
client/platforms/linux/linuxnetworkwatcher.h
Executable file → Normal file
@@ -22,6 +22,11 @@ class LinuxNetworkWatcher final : public NetworkWatcherImpl {
|
||||
|
||||
void start() override;
|
||||
|
||||
NetworkWatcherImpl::TransportType getTransportType() {
|
||||
// TODO: Find out how to do that on linux generally. (VPN-2382)
|
||||
return NetworkWatcherImpl::TransportType_Unknown;
|
||||
};
|
||||
|
||||
signals:
|
||||
void checkDevicesInThread();
|
||||
|
||||
|
||||
@@ -95,11 +95,6 @@ void MacosRouteMonitor::handleRtmDelete(const struct rt_msghdr* rtm,
|
||||
!(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) {
|
||||
return;
|
||||
}
|
||||
// Ignore interface-scoped routes, we want to find the default route to the
|
||||
// internet in the global scope.
|
||||
if (rtm->rtm_flags & RTF_IFSCOPE) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for a default route, which should have a netmask of zero.
|
||||
const struct sockaddr* sa =
|
||||
@@ -161,11 +156,6 @@ void MacosRouteMonitor::handleRtmUpdate(const struct rt_msghdr* rtm,
|
||||
!(rtm->rtm_addrs & RTA_NETMASK) || (addrlist.count() < 3)) {
|
||||
return;
|
||||
}
|
||||
// Ignore interface-scoped routes, we want to find the default route to the
|
||||
// internet in the global scope.
|
||||
if (rtm->rtm_flags & RTF_IFSCOPE) {
|
||||
return;
|
||||
}
|
||||
// Ignore route changes that we caused, or routes on the tunnel interface.
|
||||
if (rtm->rtm_index == m_ifindex) {
|
||||
return;
|
||||
|
||||
@@ -134,26 +134,26 @@ bool WireguardUtilsMacos::addInterface(const InterfaceConfig& config) {
|
||||
if (err != 0) {
|
||||
logger.error() << "Interface configuration failed:" << strerror(err);
|
||||
} else {
|
||||
if (config.m_killSwitchEnabled) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
FirewallParams params { };
|
||||
params.dnsServers.append(config.m_dnsServer);
|
||||
if (config.m_allowedIPAddressRanges.at(0).toString() == "0.0.0.0/0"){
|
||||
params.blockAll = true;
|
||||
if (config.m_excludedAddresses.size()) {
|
||||
params.allowNets = true;
|
||||
foreach (auto net, config.m_excludedAddresses) {
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
params.allowAddrs.append(net.toUtf8());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
}
|
||||
}
|
||||
applyFirewallRules(params);
|
||||
} else {
|
||||
params.blockNets = true;
|
||||
foreach (auto net, config.m_allowedIPAddressRanges) {
|
||||
params.blockAddrs.append(net.toString());
|
||||
}
|
||||
}
|
||||
|
||||
applyFirewallRules(params);
|
||||
}
|
||||
|
||||
return (err == 0);
|
||||
}
|
||||
|
||||
@@ -199,9 +199,7 @@ bool WireguardUtilsMacos::updatePeer(const InterfaceConfig& config) {
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||
if (!config.m_serverPskKey.isNull()) {
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||
|
||||
@@ -83,7 +83,7 @@ void MacOSPingSender::sendPing(const QHostAddress& dest, quint16 sequence) {
|
||||
packet.icmp_seq = htons(sequence);
|
||||
packet.icmp_cksum = inetChecksum(&packet, sizeof(packet));
|
||||
|
||||
if (sendto(m_socket, (char*)&packet, sizeof(packet), MSG_NOSIGNAL,
|
||||
if (sendto(m_socket, (char*)&packet, sizeof(packet), 0,
|
||||
(struct sockaddr*)&addr, sizeof(addr)) != sizeof(packet)) {
|
||||
logger.error() << "ping sending failed:" << strerror(errno);
|
||||
emit criticalPingError();
|
||||
@@ -107,9 +107,9 @@ void MacOSPingSender::socketReady() {
|
||||
iov.iov_base = packet;
|
||||
iov.iov_len = IP_MAXPACKET;
|
||||
|
||||
ssize_t rc = recvmsg(m_socket, &msg, MSG_DONTWAIT | MSG_NOSIGNAL);
|
||||
ssize_t rc = recvmsg(m_socket, &msg, MSG_DONTWAIT);
|
||||
if (rc <= 0) {
|
||||
logger.error() << "Recvmsg failed:" << strerror(errno);
|
||||
logger.error() << "Recvmsg failed";
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -41,33 +41,11 @@ void MacOSUtils::enableLoginItem(bool startAtBoot) {
|
||||
Q_ASSERT(appId);
|
||||
|
||||
NSString* loginItemAppId =
|
||||
QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString();
|
||||
QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString();
|
||||
CFStringRef cfs = (__bridge CFStringRef)loginItemAppId;
|
||||
|
||||
// For macOS 13 and beyond, register() and unregister() methods
|
||||
// are used for managing login items since SMLoginItemSetEnabled() is deprecated.
|
||||
// For versions prior to macOS 13, SMLoginItemSetEnabled() is used.
|
||||
if (@available(macOS 13, *)) {
|
||||
// Use register() or unregister() based on the startAtBoot flag
|
||||
NSError* error = nil;
|
||||
|
||||
if (startAtBoot) {
|
||||
if (![[SMAppService mainAppService] registerAndReturnError: & error]) {
|
||||
logger.error() << "Failed to register Amnezia VPN LoginItem: " << error.localizedDescription;
|
||||
} else {
|
||||
logger.debug() << "Amnezia VPN LoginItem registered successfully.";
|
||||
}
|
||||
} else {
|
||||
if (![[SMAppService mainAppService] unregisterAndReturnError: & error]) {
|
||||
logger.error() << "Failed to unregister Amnezia VPN LoginItem: " << error.localizedDescription;
|
||||
} else {
|
||||
logger.debug() << "LoginItem unregistered successfully.";
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CFStringRef cfs = (__bridge CFStringRef) loginItemAppId;
|
||||
Boolean ok = SMLoginItemSetEnabled(cfs, startAtBoot ? YES : NO);
|
||||
logger.debug() << "Result: " << ok;
|
||||
}
|
||||
Boolean ok = SMLoginItemSetEnabled(cfs, startAtBoot ? YES : NO);
|
||||
logger.debug() << "Result: " << ok;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
|
||||
#include "dnsutilswindows.h"
|
||||
|
||||
#include <WS2tcpip.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <windows.h>
|
||||
#include <winsock2.h>
|
||||
#include <ws2ipdef.h>
|
||||
|
||||
#include <QProcess>
|
||||
#include <QTextStream>
|
||||
@@ -42,27 +39,30 @@ DnsUtilsWindows::~DnsUtilsWindows() {
|
||||
|
||||
bool DnsUtilsWindows::updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
MIB_IF_ROW2 entry;
|
||||
if (ConvertInterfaceAliasToLuid((wchar_t*)ifname.utf16(),
|
||||
&entry.InterfaceLuid) != 0) {
|
||||
NET_LUID luid;
|
||||
if (ConvertInterfaceAliasToLuid((wchar_t*)ifname.utf16(), &luid) != 0) {
|
||||
logger.error() << "Failed to resolve LUID for" << ifname;
|
||||
return false;
|
||||
}
|
||||
if (GetIfEntry2(&entry) != NO_ERROR) {
|
||||
logger.error() << "Failed to resolve interface for" << ifname;
|
||||
return false;
|
||||
}
|
||||
m_luid = entry.InterfaceLuid.Value;
|
||||
m_luid = luid.Value;
|
||||
|
||||
logger.debug() << "Configuring DNS for" << ifname;
|
||||
if (m_setInterfaceDnsSettingsProcAddr == nullptr) {
|
||||
return updateResolversNetsh(entry.InterfaceIndex, resolvers);
|
||||
return updateResolversNetsh(resolvers);
|
||||
}
|
||||
return updateResolversWin32(entry.InterfaceGuid, resolvers);
|
||||
return updateResolversWin32(resolvers);
|
||||
}
|
||||
|
||||
bool DnsUtilsWindows::updateResolversWin32(
|
||||
GUID guid, const QList<QHostAddress>& resolvers) {
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
GUID guid;
|
||||
NET_LUID luid;
|
||||
luid.Value = m_luid;
|
||||
if (ConvertInterfaceLuidToGuid(&luid, &guid) != NO_ERROR) {
|
||||
logger.error() << "Failed to resolve GUID";
|
||||
return false;
|
||||
}
|
||||
|
||||
QStringList v4resolvers;
|
||||
QStringList v6resolvers;
|
||||
for (const QHostAddress& addr : resolvers) {
|
||||
@@ -113,8 +113,16 @@ constexpr const char* netshAddTemplate =
|
||||
"interface %1 add dnsservers name=%2 address=%3 validate=no\r\n";
|
||||
|
||||
bool DnsUtilsWindows::updateResolversNetsh(
|
||||
int ifindex, const QList<QHostAddress>& resolvers) {
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
QProcess netsh;
|
||||
NET_LUID luid;
|
||||
NET_IFINDEX ifindex;
|
||||
luid.Value = m_luid;
|
||||
if (ConvertInterfaceLuidToIndex(&luid, &ifindex) != NO_ERROR) {
|
||||
logger.error() << "Failed to resolve GUID";
|
||||
return false;
|
||||
}
|
||||
|
||||
netsh.setProgram("netsh");
|
||||
netsh.start();
|
||||
if (!netsh.waitForStarted(WINDOWS_NETSH_TIMEOUT_MSEC)) {
|
||||
@@ -158,26 +166,12 @@ bool DnsUtilsWindows::updateResolversNetsh(
|
||||
|
||||
bool DnsUtilsWindows::restoreResolvers() {
|
||||
if (m_luid == 0) {
|
||||
// If the DNS hasn't been configured, there is nothing to restore.
|
||||
return true;
|
||||
}
|
||||
|
||||
MIB_IF_ROW2 entry;
|
||||
DWORD error;
|
||||
entry.InterfaceLuid.Value = m_luid;
|
||||
error = GetIfEntry2(&entry);
|
||||
if (error == ERROR_FILE_NOT_FOUND) {
|
||||
// If the interface no longer exists, there is nothing to restore.
|
||||
return true;
|
||||
}
|
||||
if (error != NO_ERROR) {
|
||||
logger.error() << "Failed to resolve interface entry:" << error;
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<QHostAddress> empty;
|
||||
if (m_setInterfaceDnsSettingsProcAddr == nullptr) {
|
||||
return updateResolversNetsh(entry.InterfaceIndex, empty);
|
||||
return updateResolversNetsh(empty);
|
||||
}
|
||||
return updateResolversWin32(entry.InterfaceGuid, empty);
|
||||
return updateResolversWin32(empty);
|
||||
}
|
||||
|
||||
@@ -27,8 +27,8 @@ class DnsUtilsWindows final : public DnsUtils {
|
||||
quint64 m_luid = 0;
|
||||
DWORD (*m_setInterfaceDnsSettingsProcAddr)(GUID, const void*) = nullptr;
|
||||
|
||||
bool updateResolversWin32(GUID, const QList<QHostAddress>& resolvers);
|
||||
bool updateResolversNetsh(int ifindex, const QList<QHostAddress>& resolvers);
|
||||
bool updateResolversWin32(const QList<QHostAddress>& resolvers);
|
||||
bool updateResolversNetsh(const QList<QHostAddress>& resolvers);
|
||||
};
|
||||
|
||||
#endif // DNSUTILSWINDOWS_H
|
||||
|
||||
@@ -38,6 +38,7 @@ class WindowsDaemon final : public Daemon {
|
||||
Inactive,
|
||||
};
|
||||
|
||||
State m_state = Inactive;
|
||||
int m_inetAdapterIndex = -1;
|
||||
|
||||
WireguardUtilsWindows* m_wgutils = nullptr;
|
||||
|
||||
@@ -114,7 +114,7 @@ void WindowsRouteMonitor::updateValidInterfaces(int family) {
|
||||
void WindowsRouteMonitor::updateExclusionRoute(MIB_IPFORWARD_ROW2* data,
|
||||
void* ptable) {
|
||||
PMIB_IPFORWARD_TABLE2 table = reinterpret_cast<PMIB_IPFORWARD_TABLE2>(ptable);
|
||||
SOCKADDR_INET nexthop = {};
|
||||
SOCKADDR_INET nexthop = {0};
|
||||
quint64 bestLuid = 0;
|
||||
int bestMatch = -1;
|
||||
ULONG bestMetric = ULONG_MAX;
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include <QFileInfo>
|
||||
#include <QNetworkInterface>
|
||||
#include <QScopeGuard>
|
||||
#include <QThread>
|
||||
|
||||
namespace {
|
||||
Logger logger("WindowsSplitTunnel");
|
||||
@@ -30,9 +29,6 @@ WindowsSplitTunnel::WindowsSplitTunnel(QObject* parent) : QObject(parent) {
|
||||
uninstallDriver();
|
||||
return;
|
||||
}
|
||||
|
||||
m_tries = 0;
|
||||
|
||||
if (!isInstalled()) {
|
||||
logger.debug() << "Driver is not Installed, doing so";
|
||||
auto handle = installDriver();
|
||||
@@ -63,10 +59,10 @@ void WindowsSplitTunnel::initDriver() {
|
||||
m_driver = CreateFileW(DRIVER_SYMLINK, GENERIC_READ | GENERIC_WRITE, 0,
|
||||
nullptr, OPEN_EXISTING, 0, nullptr);
|
||||
;
|
||||
if (m_driver == INVALID_HANDLE_VALUE && m_tries < 500) {
|
||||
|
||||
if (m_driver == INVALID_HANDLE_VALUE) {
|
||||
WindowsUtils::windowsLog("Failed to open Driver: ");
|
||||
m_tries++;
|
||||
Sleep(100);
|
||||
|
||||
// If the handle is not present, try again after the serivce has started;
|
||||
auto driver_manager = WindowsServiceManager(DRIVER_SERVICE_NAME);
|
||||
QObject::connect(&driver_manager, &WindowsServiceManager::serviceStarted,
|
||||
|
||||
@@ -158,7 +158,6 @@ class WindowsSplitTunnel final : public QObject {
|
||||
constexpr static const auto MV_SERVICE_NAME = L"MullvadVPN";
|
||||
DRIVER_STATE getState();
|
||||
|
||||
int m_tries;
|
||||
// Initializes the WFP Sublayer
|
||||
bool initSublayer();
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ Logger logger("tunnel.dll");
|
||||
|
||||
WindowsTunnelLogger::WindowsTunnelLogger(const QString& filename,
|
||||
QObject* parent)
|
||||
: QObject(parent), m_timer(this), m_logfile(filename, this) {
|
||||
: QObject(parent), m_logfile(filename, this), m_timer(this) {
|
||||
MZ_COUNT_CTOR(WindowsTunnelLogger);
|
||||
|
||||
m_startTime = QDateTime::currentMSecsSinceEpoch() * 1000000;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "windowstunnelservice.h"
|
||||
#include "WindowsTunnelService.h"
|
||||
|
||||
#include <Windows.h>
|
||||
|
||||
@@ -30,22 +30,12 @@ static bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus);
|
||||
|
||||
WindowsTunnelService::WindowsTunnelService(QObject* parent) : QObject(parent) {
|
||||
MZ_COUNT_CTOR(WindowsTunnelService);
|
||||
logger.debug() << "WindowsTunnelService created.";
|
||||
|
||||
m_scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS);
|
||||
if (m_scm == nullptr) {
|
||||
WindowsUtils::windowsLog("Failed to open SCManager");
|
||||
}
|
||||
|
||||
// Is the service already running? Terminate it.
|
||||
SC_HANDLE service =
|
||||
OpenService((SC_HANDLE)m_scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS);
|
||||
if (service != nullptr) {
|
||||
logger.info() << "Tunnel already exists. Terminating it.";
|
||||
stopAndDeleteTunnelService(service);
|
||||
CloseServiceHandle(service);
|
||||
}
|
||||
|
||||
connect(&m_timer, &QTimer::timeout, this, &WindowsTunnelService::timeout);
|
||||
}
|
||||
|
||||
@@ -148,7 +138,7 @@ bool WindowsTunnelService::start(const QString& configData) {
|
||||
|
||||
logger.debug() << "Service:" << qApp->applicationFilePath();
|
||||
|
||||
service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Amnezia VPN (tunnel)",
|
||||
service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Amezia VPN (tunnel)",
|
||||
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
|
||||
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL,
|
||||
(const wchar_t*)serviceCmdline.utf16(), nullptr, 0,
|
||||
|
||||
@@ -116,12 +116,10 @@ bool WireguardUtilsWindows::addInterface(const InterfaceConfig& config) {
|
||||
m_luid = luid.Value;
|
||||
m_routeMonitor.setLuid(luid.Value);
|
||||
|
||||
if (config.m_killSwitchEnabled) {
|
||||
// Enable the windows firewall
|
||||
NET_IFINDEX ifindex;
|
||||
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||
WindowsFirewall::instance()->enableKillSwitch(ifindex);
|
||||
}
|
||||
// Enable the windows firewall
|
||||
NET_IFINDEX ifindex;
|
||||
ConvertInterfaceLuidToIndex(&luid, &ifindex);
|
||||
WindowsFirewall::instance()->enableKillSwitch(ifindex);
|
||||
|
||||
logger.debug() << "Registration completed";
|
||||
return true;
|
||||
@@ -139,10 +137,9 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
QByteArray pskKey =
|
||||
QByteArray::fromBase64(qPrintable(config.m_serverPskKey));
|
||||
|
||||
if (config.m_killSwitchEnabled) {
|
||||
// Enable the windows firewall for this peer.
|
||||
WindowsFirewall::instance()->enablePeerTraffic(config);
|
||||
}
|
||||
// Enable the windows firewall for this peer.
|
||||
WindowsFirewall::instance()->enablePeerTraffic(config);
|
||||
|
||||
logger.debug() << "Configuring peer" << publicKey.toHex()
|
||||
<< "via" << config.m_serverIpv4AddrIn;
|
||||
|
||||
@@ -151,9 +148,7 @@ bool WireguardUtilsWindows::updatePeer(const InterfaceConfig& config) {
|
||||
QTextStream out(&message);
|
||||
out << "set=1\n";
|
||||
out << "public_key=" << QString(publicKey.toHex()) << "\n";
|
||||
if (!config.m_serverPskKey.isNull()) {
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
}
|
||||
out << "preshared_key=" << QString(pskKey.toHex()) << "\n";
|
||||
if (!config.m_serverIpv4AddrIn.isNull()) {
|
||||
out << "endpoint=" << config.m_serverIpv4AddrIn << ":";
|
||||
} else if (!config.m_serverIpv6AddrIn.isNull()) {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <d3d11.h>
|
||||
#include <dxgi.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <shlobj_core.h>
|
||||
|
||||
#include <QDir>
|
||||
#include <QHostAddress>
|
||||
@@ -20,9 +19,9 @@
|
||||
#include "logger.h"
|
||||
#include "platforms/windows/windowsutils.h"
|
||||
|
||||
#define TUNNEL_SERVICE_NAME L"WireGuardTunnel$amnvpn"
|
||||
|
||||
constexpr const char* VPN_NAME = "AmneziaVPN";
|
||||
constexpr const char* WIREGUARD_DIR = "WireGuard";
|
||||
constexpr const char* DATA_DIR = "Data";
|
||||
|
||||
namespace {
|
||||
Logger logger("WindowsCommons");
|
||||
@@ -68,67 +67,27 @@ QString WindowsCommons::tunnelConfigFile() {
|
||||
return QString();
|
||||
}
|
||||
|
||||
// static
|
||||
QString WindowsCommons::tunnelLogFile() {
|
||||
static QString tunnelLogFilePath = getTunnelLogFilePath();
|
||||
return tunnelLogFilePath;
|
||||
}
|
||||
QStringList paths =
|
||||
QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
|
||||
|
||||
// static
|
||||
QString WindowsCommons::getProgramFilesPath() {
|
||||
wchar_t* path = nullptr;
|
||||
|
||||
if (SUCCEEDED(
|
||||
SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path))) {
|
||||
auto guard = qScopeGuard([&] { CoTaskMemFree(path); });
|
||||
return QString::fromWCharArray(path);
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
// static
|
||||
QString WindowsCommons::getTunnelLogFilePath() {
|
||||
// Return WireGuard's log file path, "\Program Files\WireGuard\Data\log.bin",
|
||||
// if the directory path exists
|
||||
auto programFilesPath = getProgramFilesPath();
|
||||
if (!programFilesPath.isEmpty()) {
|
||||
QDir programFilesDir(programFilesPath);
|
||||
|
||||
if (programFilesDir.exists()) {
|
||||
QDir wireGuardDir(programFilesDir.filePath(WIREGUARD_DIR));
|
||||
|
||||
if (wireGuardDir.exists()) {
|
||||
QDir wireGuardDataDir(wireGuardDir.filePath(DATA_DIR));
|
||||
|
||||
if (wireGuardDataDir.exists()) {
|
||||
return wireGuardDataDir.filePath("log.bin");
|
||||
}
|
||||
}
|
||||
for (const QString& path : paths) {
|
||||
QDir dir(path);
|
||||
if (!dir.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QDir vpnDir(dir.filePath(VPN_NAME));
|
||||
if (!vpnDir.exists()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return vpnDir.filePath("log.bin");
|
||||
}
|
||||
|
||||
logger.debug() << "Failed to find WireGuard Tunnel log file";
|
||||
return QString();
|
||||
}
|
||||
|
||||
// static
|
||||
int WindowsCommons::AdapterIndexTo(const QHostAddress& dst) {
|
||||
logger.debug() << "Getting Current Internet Adapter that routes to"
|
||||
<< logger.sensitive(dst.toString());
|
||||
quint32_be ipBigEndian;
|
||||
quint32 ip = dst.toIPv4Address();
|
||||
qToBigEndian(ip, &ipBigEndian);
|
||||
_MIB_IPFORWARDROW routeInfo;
|
||||
auto result = GetBestRoute(ipBigEndian, 0, &routeInfo);
|
||||
if (result != NO_ERROR) {
|
||||
return -1;
|
||||
}
|
||||
auto adapter =
|
||||
QNetworkInterface::interfaceFromIndex(routeInfo.dwForwardIfIndex);
|
||||
logger.debug() << "Internet Adapter:" << adapter.name();
|
||||
return routeInfo.dwForwardIfIndex;
|
||||
}
|
||||
|
||||
// static
|
||||
int WindowsCommons::VPNAdapterIndex() {
|
||||
// For someReason QNetworkInterface::fromName(MozillaVPN) does not work >:(
|
||||
@@ -143,7 +102,7 @@ int WindowsCommons::VPNAdapterIndex() {
|
||||
|
||||
// Static
|
||||
QString WindowsCommons::getCurrentPath() {
|
||||
QByteArray buffer(2048, 0xFFu);
|
||||
QByteArray buffer(2048, 0xFF);
|
||||
auto ok = GetModuleFileNameA(NULL, buffer.data(), buffer.size());
|
||||
|
||||
if (ok == ERROR_INSUFFICIENT_BUFFER) {
|
||||
|
||||
@@ -19,14 +19,9 @@ class WindowsCommons final {
|
||||
|
||||
// Returns the Interface Index of the VPN Adapter
|
||||
static int VPNAdapterIndex();
|
||||
// Returns the Interface Index that could Route to dst
|
||||
static int AdapterIndexTo(const QHostAddress& dst);
|
||||
|
||||
// Returns the Path of the Current process
|
||||
static QString getCurrentPath();
|
||||
|
||||
private:
|
||||
static QString getTunnelLogFilePath();
|
||||
static QString getProgramFilesPath();
|
||||
};
|
||||
|
||||
#endif // WINDOWSCOMMONS_H
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include "windowsnetworkwatcher.h"
|
||||
|
||||
#include <QNetworkInformation>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include "leakdetector.h"
|
||||
@@ -137,4 +136,9 @@ void WindowsNetworkWatcher::processWlan(PWLAN_NOTIFICATION_DATA data) {
|
||||
logger.debug() << "Unsecure network:" << logger.sensitive(ssid)
|
||||
<< "id:" << logger.sensitive(bssid);
|
||||
emit unsecuredNetwork(ssid, bssid);
|
||||
}
|
||||
}
|
||||
|
||||
NetworkWatcherImpl::TransportType WindowsNetworkWatcher::getTransportType() {
|
||||
// TODO: Implement this once we update to Qt6.3 (VPN-3511)
|
||||
return TransportType_Other;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user