mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-06-21 02:01:03 +07:00
Compare commits
146 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5bf624f02 | |||
| d8020878d5 | |||
| b027fff103 | |||
| a0c06048cd | |||
| 140b3ae781 | |||
| 53746f2f66 | |||
| 2649dba4ad | |||
| 6a1e3c07b1 | |||
| a365eff76f | |||
| 8f9acd9367 | |||
| 53fdf5f70d | |||
| 9be13ea465 | |||
| 871aced1d1 | |||
| 2254bfc128 | |||
| b71dcb8dd0 | |||
| 33d1518fd2 | |||
| ee5344a4ea | |||
| 5f67ce2e45 | |||
| abb3c918e3 | |||
| 206cccfaca | |||
| e4690cee0a | |||
| 8e56988ed0 | |||
| ff348a348c | |||
| d67c378bff | |||
| 7cf49b8602 | |||
| d85a0439c5 | |||
| a449baac78 | |||
| f721b90dc8 | |||
| b23c61b6f7 | |||
| 90d3dc5574 | |||
| 58cbba6bd2 | |||
| 9faabe9e7d | |||
| 5bd8c33a6d | |||
| 24759c92ad | |||
| 79e02daddb | |||
| 113758e381 | |||
| 9e92ee020e | |||
| 7a4f6b628b | |||
| 3bba823a2c | |||
| 7e2f223d7f | |||
| 20c72ec97c | |||
| eb48e4b668 | |||
| 9ace09a604 | |||
| 702735c2ca | |||
| 174f2ac3db | |||
| e3b5b4a9d9 | |||
| 72ba012765 | |||
| 0f9bbcd060 | |||
| a9d038d8bf | |||
| 54a6845315 | |||
| 0c7059a476 | |||
| 5bed92ab0b | |||
| 49a14785c6 | |||
| 2c78c06dda | |||
| cf8a0efd0d | |||
| 5211cdd4c0 | |||
| e5cdf9e397 | |||
| 126d2362bb | |||
| 79ef2cda93 | |||
| 17585bc8c8 | |||
| d10aa43d8b | |||
| 030ed66b21 | |||
| 098577ec91 | |||
| 6b0f1ed429 | |||
| 4bde1ccb44 | |||
| 4671b3a96f | |||
| 03c18c44e2 | |||
| 72ffc7ce6a | |||
| 87b738ef16 | |||
| b868831bcb | |||
| 477d7214c5 | |||
| f3cd3d4f06 | |||
| aea4cc2389 | |||
| 245aa8eb8c | |||
| f14a2add0f | |||
| 89703ba58f | |||
| 23715fca8b | |||
| d90685600e | |||
| f007e5eb5c | |||
| a8ccea00c7 | |||
| cd2ee00769 | |||
| c98a418807 | |||
| 0e4ae26bae | |||
| d50e7dd3f4 | |||
| f0085f52eb | |||
| 5c19b08e5e | |||
| 79edbe52a3 | |||
| 0dd181bb5b | |||
| d8682003fa | |||
| f4a2cf9984 | |||
| 98e6358fd3 | |||
| af90065d2e | |||
| f372f4074b | |||
| 6a2e5f83a1 | |||
| a2badd46c4 | |||
| 8623a983b8 | |||
| 151e662027 | |||
| f588fe29db | |||
| 030b0351a2 | |||
| d4453a5f38 | |||
| 2252905596 | |||
| ec650a65f7 | |||
| 6953f8d814 | |||
| 624a84cbfb | |||
| 506d9793e1 | |||
| ef52f6ab08 | |||
| 5312a6e885 | |||
| fdd600794e | |||
| 7bfbdca72a | |||
| a22c08a41d | |||
| 10ea9b418a | |||
| e39efb1d68 | |||
| 7db84122f9 | |||
| 84ad167ab4 | |||
| ed7e217a6b | |||
| c1b0d4a4a7 | |||
| 2f84e24353 | |||
| f73586185b | |||
| 653ffb9a68 | |||
| fe8c2d157a | |||
| 86367a1276 | |||
| b0fcf92ada | |||
| 283b6ebf81 | |||
| d0a7fc5116 | |||
| 9851aacba7 | |||
| 51f9fb9e0a | |||
| 0325761f3e | |||
| a6ca1b12da | |||
| 82a9e7e27d | |||
| f5301e1315 | |||
| adab30fc81 | |||
| e7bd24f065 | |||
| 2ec448ba13 | |||
| c6e6f2ae84 | |||
| e9468a4c2f | |||
| 45de951897 | |||
| db8d966fac | |||
| 6b69bc9618 | |||
| 0089b0b799 | |||
| e4841e809b | |||
| ba4237f1dd | |||
| f6acec53c0 | |||
| 5f631eaa76 | |||
| 7730dd510c | |||
| 30bd264f17 | |||
| 5206665fa0 |
@@ -233,7 +233,7 @@ jobs:
|
|||||||
- name: 'Setup xcode'
|
- name: 'Setup xcode'
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '13.4'
|
xcode-version: '14.3.1'
|
||||||
|
|
||||||
- name: 'Install Qt'
|
- name: 'Install Qt'
|
||||||
uses: jurplel/install-qt-action@v3
|
uses: jurplel/install-qt-action@v3
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
|
|||||||
|
|
||||||
set(PROJECT AmneziaVPN)
|
set(PROJECT AmneziaVPN)
|
||||||
|
|
||||||
project(${PROJECT} VERSION 4.4.2.1
|
project(${PROJECT} VERSION 4.5.3.0
|
||||||
DESCRIPTION "AmneziaVPN"
|
DESCRIPTION "AmneziaVPN"
|
||||||
HOMEPAGE_URL "https://amnezia.org/"
|
HOMEPAGE_URL "https://amnezia.org/"
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
|
|||||||
set(RELEASE_DATE "${CURRENT_DATE}")
|
set(RELEASE_DATE "${CURRENT_DATE}")
|
||||||
|
|
||||||
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
|
||||||
set(APP_ANDROID_VERSION_CODE 49)
|
set(APP_ANDROID_VERSION_CODE 52)
|
||||||
|
|
||||||
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
|
||||||
set(MZ_PLATFORM_NAME "linux")
|
set(MZ_PLATFORM_NAME "linux")
|
||||||
|
|||||||
+1
-1
Submodule client/3rd-prebuilt updated: ab4e6b680d...eb43e90f38
Vendored
+1
-1
Submodule client/3rd/OpenVPNAdapter updated: 6f71d0743d...7c821a8d5c
@@ -0,0 +1,2 @@
|
|||||||
|
*.user
|
||||||
|
build/
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.5)
|
||||||
|
|
||||||
|
project(QJsonStruct LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
option(BUILD_TESTING ON)
|
||||||
|
|
||||||
|
include(QJsonStruct.cmake)
|
||||||
|
find_package(Qt5 COMPONENTS Core REQUIRED)
|
||||||
|
|
||||||
|
if(BUILD_TESTING)
|
||||||
|
include(CTest)
|
||||||
|
add_subdirectory(test)
|
||||||
|
endif()
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2020 Qv2ray Workgroup
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonValue>
|
||||||
|
#include <QList>
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
enum class QJsonIOPathType
|
||||||
|
{
|
||||||
|
JSONIO_MODE_ARRAY,
|
||||||
|
JSONIO_MODE_OBJECT
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef QPair<QString, QJsonIOPathType> QJsonIONodeType;
|
||||||
|
|
||||||
|
struct QJsonIOPath : QList<QJsonIONodeType>
|
||||||
|
{
|
||||||
|
template<typename type1, typename type2, typename... types>
|
||||||
|
QJsonIOPath(const type1 t1, const type2 t2, const types... ts)
|
||||||
|
{
|
||||||
|
AppendPath(t1);
|
||||||
|
AppendPath(t2);
|
||||||
|
(AppendPath(ts), ...);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendPath(size_t index)
|
||||||
|
{
|
||||||
|
append({ QString::number(index), QJsonIOPathType::JSONIO_MODE_ARRAY });
|
||||||
|
}
|
||||||
|
|
||||||
|
void AppendPath(const QString &key)
|
||||||
|
{
|
||||||
|
append({ key, QJsonIOPathType::JSONIO_MODE_OBJECT });
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath &operator<<(const t &str)
|
||||||
|
{
|
||||||
|
AppendPath(str);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath &operator+=(const t &val)
|
||||||
|
{
|
||||||
|
AppendPath(val);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonIOPath &operator<<(const QJsonIOPath &other)
|
||||||
|
{
|
||||||
|
for (const auto &x : other)
|
||||||
|
this->append(x);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath &operator<<(const t &val) const
|
||||||
|
{
|
||||||
|
auto _new = *this;
|
||||||
|
return _new << val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename t>
|
||||||
|
QJsonIOPath operator+(const t &val) const
|
||||||
|
{
|
||||||
|
auto _new = *this;
|
||||||
|
return _new << val;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonIOPath operator+(const QJsonIOPath &other) const
|
||||||
|
{
|
||||||
|
auto _new = *this;
|
||||||
|
for (const auto &x : other)
|
||||||
|
_new.append(x);
|
||||||
|
return _new;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class QJsonIO
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
const static inline QJsonValue Null = QJsonValue::Null;
|
||||||
|
const static inline QJsonValue Undefined = QJsonValue::Undefined;
|
||||||
|
|
||||||
|
template<typename current_key_type, typename... t_other_types>
|
||||||
|
static QJsonValue GetValue(const QJsonValue &parent, const current_key_type ¤t, const t_other_types &...other)
|
||||||
|
{
|
||||||
|
if constexpr (sizeof...(t_other_types) == 0)
|
||||||
|
if constexpr (std::is_integral_v<current_key_type>)
|
||||||
|
return parent.toArray()[current];
|
||||||
|
else
|
||||||
|
return parent.toObject()[current];
|
||||||
|
else if constexpr (std::is_integral_v<current_key_type>)
|
||||||
|
return GetValue(parent.toArray()[current], other...);
|
||||||
|
else
|
||||||
|
return GetValue(parent.toObject()[current], other...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename... key_types_t>
|
||||||
|
static QJsonValue GetValue(QJsonValue value, const std::tuple<key_types_t...> &keys, const QJsonValue &defaultValue = Undefined)
|
||||||
|
{
|
||||||
|
std::apply([&](auto &&...args) { ((value = value[args]), ...); }, keys);
|
||||||
|
return value.isUndefined() ? defaultValue : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename parent_type, typename t_value_type, typename current_key_type, typename... t_other_key_types>
|
||||||
|
static void SetValue(parent_type &parent, const t_value_type &val, const current_key_type ¤t, const t_other_key_types &...other)
|
||||||
|
{
|
||||||
|
// If current parent is an array, increase its size to fit the "key"
|
||||||
|
if constexpr (std::is_integral_v<current_key_type>)
|
||||||
|
for (auto i = parent.size(); i <= current; i++)
|
||||||
|
parent.insert(i, {});
|
||||||
|
|
||||||
|
// If the t_other_key_types has nothing....
|
||||||
|
// Means we have reached the end of recursion.
|
||||||
|
if constexpr (sizeof...(t_other_key_types) == 0)
|
||||||
|
parent[current] = val;
|
||||||
|
else if constexpr (std::is_integral_v<typename std::tuple_element_t<0, std::tuple<t_other_key_types...>>>)
|
||||||
|
{
|
||||||
|
// Means we still have many keys
|
||||||
|
// So this element is an array.
|
||||||
|
auto _array = parent[current].toArray();
|
||||||
|
SetValue(_array, val, other...);
|
||||||
|
parent[current] = _array;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto _object = parent[current].toObject();
|
||||||
|
SetValue(_object, val, other...);
|
||||||
|
parent[current] = _object;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static QJsonValue GetValue(const QJsonValue &parent, const QJsonIOPath &path, const QJsonValue &defaultValue = QJsonIO::Undefined)
|
||||||
|
{
|
||||||
|
QJsonValue val = parent;
|
||||||
|
for (const auto &[k, t] : path)
|
||||||
|
{
|
||||||
|
if (t == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||||
|
val = val.toArray()[k.toInt()];
|
||||||
|
else
|
||||||
|
val = val.toObject()[k];
|
||||||
|
}
|
||||||
|
return val.isUndefined() ? defaultValue : val;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename parent_type, typename value_type>
|
||||||
|
static void SetValue(parent_type &parent, const value_type &t, const QJsonIOPath &path)
|
||||||
|
{
|
||||||
|
QList<std::tuple<QString, QJsonIOPathType, QJsonValue>> _stack;
|
||||||
|
QJsonValue lastNode = parent;
|
||||||
|
for (const auto &[key, type] : path)
|
||||||
|
{
|
||||||
|
_stack.prepend({ key, type, lastNode });
|
||||||
|
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||||
|
lastNode = lastNode.toArray().at(key.toInt());
|
||||||
|
else
|
||||||
|
lastNode = lastNode.toObject()[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
lastNode = t;
|
||||||
|
|
||||||
|
for (const auto &[key, type, node] : _stack)
|
||||||
|
{
|
||||||
|
if (type == QJsonIOPathType::JSONIO_MODE_ARRAY)
|
||||||
|
{
|
||||||
|
const auto index = key.toInt();
|
||||||
|
auto nodeArray = node.toArray();
|
||||||
|
for (auto i = nodeArray.size(); i <= index; i++)
|
||||||
|
nodeArray.insert(i, {});
|
||||||
|
nodeArray[index] = lastNode;
|
||||||
|
lastNode = nodeArray;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto nodeObject = node.toObject();
|
||||||
|
nodeObject[key] = lastNode;
|
||||||
|
lastNode = nodeObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (std::is_same_v<parent_type, QJsonObject>)
|
||||||
|
parent = lastNode.toObject();
|
||||||
|
else if constexpr (std::is_same_v<parent_type, QJsonArray>)
|
||||||
|
parent = lastNode.toArray();
|
||||||
|
else
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
include_directories(${CMAKE_CURRENT_LIST_DIR})
|
||||||
|
|
||||||
|
set(QJSONSTRUCT_SOURCES
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/QJsonStruct.hpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/QJsonIO.hpp)
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "macroexpansion.hpp"
|
||||||
|
|
||||||
|
#ifndef _X
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QVariant>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/// macro to define an operator==
|
||||||
|
#define ___JSONSTRUCT_DEFAULT_COMPARE_IMPL(x) (this->x == ___another___instance__.x) &&
|
||||||
|
#define JSONSTRUCT_COMPARE(CLASS, ...) \
|
||||||
|
bool operator==(const CLASS &___another___instance__) const \
|
||||||
|
{ \
|
||||||
|
return FOR_EACH(___JSONSTRUCT_DEFAULT_COMPARE_IMPL, __VA_ARGS__) true; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// Load JSON IMPL
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL(name) name::loadJson(___json_object_);
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC(name) ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC(name) \
|
||||||
|
if (___json_object_.toObject().contains(#name)) \
|
||||||
|
{ \
|
||||||
|
JsonStructHelper::Deserialize(this->name, ___json_object_.toObject()[#name]); \
|
||||||
|
} \
|
||||||
|
else \
|
||||||
|
{ \
|
||||||
|
this->name = ___qjsonstruct_default_check.name; \
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// To JSON IMPL
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_F_FUNC(name) \
|
||||||
|
if (!(___qjsonstruct_default_check.name == this->name)) \
|
||||||
|
{ \
|
||||||
|
___json_object_.insert(#name, JsonStructHelper::Serialize(name)); \
|
||||||
|
}
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_A_FUNC(name) ___json_object_.insert(#name, JsonStructHelper::Serialize(name));
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL(name) JsonStructHelper::MergeJson(___json_object_, name::toJson());
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_B_FUNC(...) FOREACH_CALL_FUNC_3(___SERIALIZE_TO_JSON_CONVERT_B_FUNC_IMPL, __VA_ARGS__)
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// Load JSON Wrapper
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___DESERIALIZE_FROM_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||||
|
#define ___DESERIALIZE_FROM_JSON_EXTRACT_B_F(name_option) ___DESERIALIZE_FROM_JSON_CONVERT_FUNC_DECL_##name_option
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
// To JSON Wrapper
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_A(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_A_FUNC, __VA_ARGS__)
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_F(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_F_FUNC, __VA_ARGS__)
|
||||||
|
#define ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_B(...) FOREACH_CALL_FUNC_2(___SERIALIZE_TO_JSON_CONVERT_B_FUNC, __VA_ARGS__)
|
||||||
|
#define ___SERIALIZE_TO_JSON_EXTRACT_B_F(name_option) ___SERIALIZE_TO_JSON_CONVERT_FUNC_DECL_##name_option
|
||||||
|
|
||||||
|
// ============================================================================================
|
||||||
|
#define JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, ...) \
|
||||||
|
void loadJson(const QJsonValue &___json_object_) \
|
||||||
|
{ \
|
||||||
|
___class_type_ ___qjsonstruct_default_check; \
|
||||||
|
FOREACH_CALL_FUNC(___DESERIALIZE_FROM_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||||
|
} \
|
||||||
|
[[nodiscard]] const QJsonObject toJson() const \
|
||||||
|
{ \
|
||||||
|
___class_type_ ___qjsonstruct_default_check; \
|
||||||
|
QJsonObject ___json_object_; \
|
||||||
|
FOREACH_CALL_FUNC(___SERIALIZE_TO_JSON_EXTRACT_B_F, __VA_ARGS__); \
|
||||||
|
return ___json_object_; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define JSONSTRUCT_REGISTER(___class_type_, ...) \
|
||||||
|
JSONSTRUCT_REGISTER_NOCOPYMOVE(___class_type_, __VA_ARGS__); \
|
||||||
|
[[nodiscard]] static auto fromJson(const QJsonValue &___json_object_) \
|
||||||
|
{ \
|
||||||
|
___class_type_ _t; \
|
||||||
|
_t.loadJson(___json_object_); \
|
||||||
|
return _t; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(type, convert_func) \
|
||||||
|
static void Deserialize(type &t, const QJsonValue &d) \
|
||||||
|
{ \
|
||||||
|
t = d.convert_func; \
|
||||||
|
}
|
||||||
|
|
||||||
|
class JsonStructHelper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void MergeJson(QJsonObject &mergeTo, const QJsonObject &mergeIn)
|
||||||
|
{
|
||||||
|
for (const auto &key : mergeIn.keys())
|
||||||
|
mergeTo[key] = mergeIn.value(key);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
template<typename T>
|
||||||
|
static void Deserialize(T &t, const QJsonValue &d)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_enum_v<T>)
|
||||||
|
t = (T) d.toInt();
|
||||||
|
else if constexpr (std::is_same_v<T, QJsonObject>)
|
||||||
|
t = d.toObject();
|
||||||
|
else if constexpr (std::is_same_v<T, QJsonArray>)
|
||||||
|
t = d.toArray();
|
||||||
|
else
|
||||||
|
t.loadJson(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QString, toString());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(QChar, toVariant().toChar());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::string, toString().toStdString());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(std::wstring, toString().toStdWString());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(bool, toBool());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(double, toDouble());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(float, toVariant().toFloat());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(int, toInt());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long, toVariant().toLongLong());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(long long, toVariant().toLongLong());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned int, toVariant().toUInt());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long, toVariant().toULongLong());
|
||||||
|
___DECL_JSON_STRUCT_LOAD_SIMPLE_TYPE_FUNC(unsigned long long, toVariant().toULongLong());
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static void Deserialize(QList<T> &t, const QJsonValue &d)
|
||||||
|
{
|
||||||
|
t.clear();
|
||||||
|
for (const auto &val : d.toArray())
|
||||||
|
{
|
||||||
|
T data;
|
||||||
|
Deserialize(data, val);
|
||||||
|
t.push_back(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename TKey, typename TValue>
|
||||||
|
static void Deserialize(QMap<TKey, TValue> &t, const QJsonValue &d)
|
||||||
|
{
|
||||||
|
t.clear();
|
||||||
|
const auto &jsonObject = d.toObject();
|
||||||
|
TKey keyVal;
|
||||||
|
TValue valueVal;
|
||||||
|
for (const auto &key : jsonObject.keys())
|
||||||
|
{
|
||||||
|
Deserialize(keyVal, key);
|
||||||
|
Deserialize(valueVal, jsonObject.value(key));
|
||||||
|
t.insert(keyVal, valueVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================== Store Json Data ===========================
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static QJsonValue Serialize(const T &t)
|
||||||
|
{
|
||||||
|
if constexpr (std::is_enum_v<T>)
|
||||||
|
return (int) t;
|
||||||
|
else if constexpr (std::is_same_v<T, QJsonObject> || std::is_same_v<T, QJsonArray>)
|
||||||
|
return t;
|
||||||
|
else
|
||||||
|
return t.toJson();
|
||||||
|
}
|
||||||
|
|
||||||
|
#define pure_func(x) (x)
|
||||||
|
#define ___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(type) \
|
||||||
|
static QJsonValue Serialize(const type &t) \
|
||||||
|
{ \
|
||||||
|
return QJsonValue(t); \
|
||||||
|
}
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(int);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(bool);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonArray);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QJsonObject);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(QString);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(long long);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(float);
|
||||||
|
___DECL_JSON_STRUCT_STORE_SIMPLE_TYPE_FUNC(double);
|
||||||
|
|
||||||
|
#define ___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(type, func) \
|
||||||
|
static QJsonValue Serialize(const type &t) \
|
||||||
|
{ \
|
||||||
|
return QJsonValue::fromVariant(func); \
|
||||||
|
}
|
||||||
|
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::string, QString::fromStdString(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(std::wstring, QString::fromStdWString(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(long, QVariant::fromValue<long>(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned int, QVariant::fromValue<unsigned int>(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long, QVariant::fromValue<unsigned long>(t))
|
||||||
|
___DECL_JSON_STRUCT_STORE_VARIANT_TYPE_FUNC(unsigned long long, QVariant::fromValue<unsigned long long>(t))
|
||||||
|
|
||||||
|
template<typename TValue>
|
||||||
|
static QJsonValue Serialize(const QMap<QString, TValue> &t)
|
||||||
|
{
|
||||||
|
QJsonObject mapObject;
|
||||||
|
for (const auto &key : t.keys())
|
||||||
|
{
|
||||||
|
auto valueVal = Serialize(t.value(key));
|
||||||
|
mapObject.insert(key, valueVal);
|
||||||
|
}
|
||||||
|
return mapObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
static QJsonValue Serialize(const QList<T> &t)
|
||||||
|
{
|
||||||
|
QJsonArray listObject;
|
||||||
|
for (const auto &item : t)
|
||||||
|
{
|
||||||
|
listObject.push_back(Serialize(item));
|
||||||
|
}
|
||||||
|
return listObject;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define CONCATENATE1(arg1, arg2) CONCATENATE2(arg1, arg2)
|
||||||
|
#define CONCATENATE2(arg1, arg2) arg1##arg2
|
||||||
|
#define CONCATENATE(x, y) x##y
|
||||||
|
|
||||||
|
#define EXPAND(...) __VA_ARGS__
|
||||||
|
#define FOR_EACH_1(what, x, ...) what(x)
|
||||||
|
#define FOR_EACH_2(what, x, ...) what(x) EXPAND(FOR_EACH_1(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_3(what, x, ...) what(x) EXPAND(FOR_EACH_2(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_4(what, x, ...) what(x) EXPAND(FOR_EACH_3(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_5(what, x, ...) what(x) EXPAND(FOR_EACH_4(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_6(what, x, ...) what(x) EXPAND(FOR_EACH_5(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_7(what, x, ...) what(x) EXPAND(FOR_EACH_6(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_8(what, x, ...) what(x) EXPAND(FOR_EACH_7(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_9(what, x, ...) what(x) EXPAND(FOR_EACH_8(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_10(what, x, ...) what(x) EXPAND(FOR_EACH_9(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_11(what, x, ...) what(x) EXPAND(FOR_EACH_10(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_12(what, x, ...) what(x) EXPAND(FOR_EACH_11(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_13(what, x, ...) what(x) EXPAND(FOR_EACH_12(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_14(what, x, ...) what(x) EXPAND(FOR_EACH_13(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_15(what, x, ...) what(x) EXPAND(FOR_EACH_14(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH_16(what, x, ...) what(x) EXPAND(FOR_EACH_15(what, __VA_ARGS__))
|
||||||
|
|
||||||
|
#define FOR_EACH_NARG(...) FOR_EACH_NARG_(__VA_ARGS__, FOR_EACH_RSEQ_N())
|
||||||
|
#define FOR_EACH_NARG_(...) EXPAND(FOR_EACH_ARG_N(__VA_ARGS__))
|
||||||
|
#define FOR_EACH_ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, N, ...) N
|
||||||
|
#define FOR_EACH_RSEQ_N() 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
||||||
|
|
||||||
|
#define FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(FOR_EACH_, N)(what, __VA_ARGS__))
|
||||||
|
#define FOR_EACH(what, ...) FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||||
|
#define FOREACH_CALL_FUNC(func, ...) FOR_EACH(func, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Bad hack ==========================================================================================================================
|
||||||
|
#define _2X_FOR_EACH_1(what, x, ...) what(x)
|
||||||
|
#define _2X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_1(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_2(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_3(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_4(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_5(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_6(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_7(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_8(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_9(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_10(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_11(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_12(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_13(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_14(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_2X_FOR_EACH_15(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_2X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||||
|
#define _2X_FOR_EACH(what, ...) _2X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||||
|
#define FOREACH_CALL_FUNC_2(func, ...) _2X_FOR_EACH(func, __VA_ARGS__)
|
||||||
|
|
||||||
|
// Bad hack ==========================================================================================================================
|
||||||
|
#define _3X_FOR_EACH_1(what, x, ...) what(x)
|
||||||
|
#define _3X_FOR_EACH_2(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_1(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_3(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_2(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_4(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_3(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_5(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_4(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_6(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_5(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_7(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_6(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_8(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_7(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_9(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_8(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_10(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_9(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_11(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_10(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_12(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_11(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_13(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_12(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_14(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_13(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_15(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_14(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_16(what, x, ...) what(x) EXPAND(_3X_FOR_EACH_15(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH_(N, what, ...) EXPAND(CONCATENATE(_3X_FOR_EACH_, N)(what, __VA_ARGS__))
|
||||||
|
#define _3X_FOR_EACH(what, ...) _3X_FOR_EACH_(FOR_EACH_NARG(__VA_ARGS__), what, __VA_ARGS__)
|
||||||
|
#define FOREACH_CALL_FUNC_3(func, ...) _3X_FOR_EACH(func, __VA_ARGS__)
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
function(QJSONSTRUCT_ADD_TEST TEST_NAME TEST_SOURCE)
|
||||||
|
add_executable(${TEST_NAME} ${TEST_SOURCE} catch.hpp ${QJSONSTRUCT_SOURCES})
|
||||||
|
target_include_directories(${TEST_NAME}
|
||||||
|
PRIVATE
|
||||||
|
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
${TEST_NAME}
|
||||||
|
PRIVATE
|
||||||
|
Qt::Core
|
||||||
|
)
|
||||||
|
add_test(NAME QJSONSTRUCT_TEST_${TEST_NAME} COMMAND $<TARGET_FILE:${TEST_NAME}> -s)
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
QJSONSTRUCT_ADD_TEST(serialization serialize/main.cpp)
|
||||||
|
#QJSONSTRUCT_ADD_TEST(serialize_strings serialize/strings.cpp)
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
#ifndef _X
|
||||||
|
#include <QList>
|
||||||
|
#include <QString>
|
||||||
|
#include <QStringList>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct BaseStruct
|
||||||
|
{
|
||||||
|
QString baseStr;
|
||||||
|
int o;
|
||||||
|
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr, o))
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BaseStruct2
|
||||||
|
{
|
||||||
|
QString baseStr2;
|
||||||
|
int o2;
|
||||||
|
JSONSTRUCT_REGISTER(BaseStruct, F(baseStr2, o2))
|
||||||
|
};
|
||||||
|
struct TestInnerStruct
|
||||||
|
: BaseStruct
|
||||||
|
, BaseStruct2
|
||||||
|
{
|
||||||
|
QJsonObject jobj;
|
||||||
|
QJsonArray jarray;
|
||||||
|
QString str;
|
||||||
|
JSONSTRUCT_REGISTER(TestInnerStruct, B(BaseStruct, BaseStruct2), F(str, jobj, jarray))
|
||||||
|
};
|
||||||
|
|
||||||
|
struct JsonIOTest
|
||||||
|
{
|
||||||
|
QString str;
|
||||||
|
QList<int> listOfNumber;
|
||||||
|
QList<bool> listOfBool;
|
||||||
|
QList<QString> listOfString;
|
||||||
|
QList<QList<QString>> listOfListOfString;
|
||||||
|
|
||||||
|
QMap<QString, QString> map;
|
||||||
|
TestInnerStruct inner;
|
||||||
|
|
||||||
|
JSONSTRUCT_REGISTER(JsonIOTest, F(str, listOfNumber, listOfBool, listOfString, listOfListOfString, map, inner));
|
||||||
|
JsonIOTest(){};
|
||||||
|
};
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
|
||||||
|
struct SubData
|
||||||
|
{
|
||||||
|
QString subString;
|
||||||
|
JSONSTRUCT_REGISTER_TOJSON(subString)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ToJsonOnlyData
|
||||||
|
{
|
||||||
|
QString x;
|
||||||
|
int y;
|
||||||
|
int z;
|
||||||
|
QList<int> ints;
|
||||||
|
SubData sub;
|
||||||
|
QMap<QString, SubData> subs;
|
||||||
|
JSONSTRUCT_REGISTER_TOJSON(x, y, z, sub, ints, subs)
|
||||||
|
};
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,70 @@
|
|||||||
|
#include "QJsonIO.hpp"
|
||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
#include "TestIO.hpp"
|
||||||
|
#include "TestOut.hpp"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
Q_UNUSED(argc)
|
||||||
|
Q_UNUSED(argv)
|
||||||
|
|
||||||
|
{
|
||||||
|
ToJsonOnlyData data;
|
||||||
|
data.x = "1string";
|
||||||
|
data.y = 2;
|
||||||
|
data.ints << 0;
|
||||||
|
data.ints << 100;
|
||||||
|
data.ints << 900;
|
||||||
|
data.sub.subString = "subs";
|
||||||
|
data.subs["subs-1"] = { "subs1-data" };
|
||||||
|
data.subs["subs-2"] = { "subs2-data" };
|
||||||
|
data.subs["subs-3"] = { "subs3-data" };
|
||||||
|
data.z = 3;
|
||||||
|
auto x = data.toJson();
|
||||||
|
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
{
|
||||||
|
auto f = JsonIOTest::fromJson( //
|
||||||
|
QJsonObject{
|
||||||
|
{ "inner", QJsonObject{ { "str", "innerString" }, //
|
||||||
|
{ "jobj", QJsonObject{ { "key", "value" } } }, //
|
||||||
|
{ "jarray", QJsonArray{ "array0", "array1", "array2" } }, //
|
||||||
|
{ "baseStr", "baseInnerString" } } }, //
|
||||||
|
{ "str", "data1" }, //
|
||||||
|
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||||
|
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||||
|
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||||
|
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||||
|
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||||
|
QJsonArray{ "1", "2" }, //
|
||||||
|
QJsonArray{ "1", "2", "3" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||||
|
});
|
||||||
|
auto x = f.toJson();
|
||||||
|
std::cout << QJsonDocument(x).toJson().toStdString() << std::endl;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
QJsonObject obj{
|
||||||
|
{ "inner", QJsonObject{ { "str", "innerString" }, { "baseStr", "baseInnerString" } } }, //
|
||||||
|
{ "str", "data1" }, //
|
||||||
|
{ "map", QJsonObject{ { "mapStr", "mapData" } } }, //
|
||||||
|
{ "listOfString", QJsonArray{ "1", "2", "3", "4", "5" } }, //
|
||||||
|
{ "listOfNumber", QJsonArray{ 1, 2, 3, 4, 5 } }, //
|
||||||
|
{ "listOfBool", QJsonArray{ true, false, false, true, true } }, //
|
||||||
|
{ "listOfListOfString", QJsonArray{ QJsonArray{ "1" }, //
|
||||||
|
QJsonArray{ "1", "2" }, //
|
||||||
|
QJsonArray{ "1", "2", "3" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4" }, //
|
||||||
|
QJsonArray{ "1", "2", "3", "4", "5" } } }, //
|
||||||
|
};
|
||||||
|
auto y = QJsonIO::GetValue(obj, std::tuple{ "listOfListOfString", 2 });
|
||||||
|
y.toObject();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
#include "QJsonStruct.hpp"
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include "catch.hpp"
|
||||||
|
|
||||||
|
const static inline auto INT_TEST_MAX = std::numeric_limits<int>::max() - 1;
|
||||||
|
const static inline auto INT_TEST_MIN = -(std::numeric_limits<int>::min() + 1);
|
||||||
|
|
||||||
|
#define SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance) \
|
||||||
|
class CLASS \
|
||||||
|
{ \
|
||||||
|
public: \
|
||||||
|
TYPE field = defaultvalue; \
|
||||||
|
JSONSTRUCT_REGISTER(CLASS, existance(field)); \
|
||||||
|
};
|
||||||
|
|
||||||
|
// SINGLE_ELEMENT_REQUIRE( CLASS_NAME , TYPE , FIELD , DEFAULT_VALUE , SET VALUE , CHECK VALUE )
|
||||||
|
#define SINGLE_ELEMENT_REQUIRE(CLASS, TYPE, field, defaultvalue, value, checkvalue, existance) \
|
||||||
|
SINGLE_ELEMENT_CLASS_DECL(CLASS, TYPE, field, defaultvalue, existance); \
|
||||||
|
CLASS CLASS##_class; \
|
||||||
|
CLASS##_class.field = value; \
|
||||||
|
REQUIRE(CLASS##_class.toJson()[#field] == checkvalue);
|
||||||
|
|
||||||
|
using namespace std;
|
||||||
|
SCENARIO("Test Serialization", "[Serialize]")
|
||||||
|
{
|
||||||
|
GIVEN("Single Element")
|
||||||
|
{
|
||||||
|
const static QList<QString> defaultList{ "entry 1", "entry 2" };
|
||||||
|
const static QMap<QString, QString> defaultMap{ { "key1", "value1" }, { "key2", "value2" } };
|
||||||
|
typedef QMap<QString, QString> QStringQStringMap;
|
||||||
|
|
||||||
|
WHEN("Serialize a single element")
|
||||||
|
{
|
||||||
|
const static QStringQStringMap setValueMap{ { "newkey1", "newvalue1" } };
|
||||||
|
const static QJsonObject setValueJson{ { "newkey1", QJsonValue{ "newvalue1" } } };
|
||||||
|
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_Empty, QString, a, "empty", "", "", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest, QString, a, "empty", "Some QString", "Some QString", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_WithQoutes, QString, a, "empty", "\"", "\"", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_zint, int, a, -10, 0, 0, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_nint, int, a, -10, 1, 1, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_pint, int, a, -10, -1, -1, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_pmint, int, a, -1, INT_TEST_MAX, INT_TEST_MAX, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_zmint, int, a, -1, INT_TEST_MIN, INT_TEST_MIN, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_zuint, uint, a, -10, 0, 0, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QStringTest_puint, uint, a, -10, 1, 1, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(BoolTest_True, bool, a, false, true, true, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(BoolTest_False, bool, a, true, false, false, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(StdStringTest, string, a, "def", "std::string _test", "std::string _test", F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QListTest, QList<QString>, a, defaultList, { "newEntry" }, QJsonArray{ "newEntry" }, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QMapTest, QStringQStringMap, a, defaultMap, {}, QJsonObject{}, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(QMapValueTest, QStringQStringMap, a, defaultMap, setValueMap, setValueJson, F);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Serialize a default value")
|
||||||
|
{
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", QJsonValue::Undefined, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, QJsonValue::Undefined, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, QJsonValue::Undefined, F);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, QJsonValue::Undefined, F);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Serialize a force existance default value")
|
||||||
|
{
|
||||||
|
const static QJsonArray defaultListJson{ "entry 1", "entry 2" };
|
||||||
|
const static QJsonObject defaultMapJson{ { "key1", "value1" }, { "key2", "value2" } };
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultQString, QString, a, "defaultvalue", "defaultvalue", "defaultvalue", A);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultInteger, int, a, 12345, 12345, 12345, A);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultList, QList<QString>, a, defaultList, defaultList, defaultListJson, A);
|
||||||
|
SINGLE_ELEMENT_REQUIRE(DefaultMap, QStringQStringMap, a, defaultMap, defaultMap, defaultMapJson, A);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN("Multiple Simple Elements")
|
||||||
|
{
|
||||||
|
WHEN("Can Omit Default Value")
|
||||||
|
{
|
||||||
|
class MultipleNonDefaultElementTestClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString astring;
|
||||||
|
int integer = 0;
|
||||||
|
double adouble = 0.0;
|
||||||
|
QList<QString> myList;
|
||||||
|
JSONSTRUCT_REGISTER(MultipleNonDefaultElementTestClass, F(astring, integer, adouble, myList))
|
||||||
|
};
|
||||||
|
MultipleNonDefaultElementTestClass instance;
|
||||||
|
const auto json = instance.toJson();
|
||||||
|
REQUIRE(json["astring"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["integer"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["adouble"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["myList"] == QJsonValue::Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Forcing Existance")
|
||||||
|
{
|
||||||
|
class MultipleNonDefaultExistanceElementTestClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QString astring;
|
||||||
|
int integer = 0;
|
||||||
|
double adouble = 0.0;
|
||||||
|
QList<QString> myList;
|
||||||
|
JSONSTRUCT_REGISTER(MultipleNonDefaultExistanceElementTestClass, A(astring, integer, adouble, myList))
|
||||||
|
};
|
||||||
|
MultipleNonDefaultExistanceElementTestClass instance;
|
||||||
|
const auto json = instance.toJson();
|
||||||
|
REQUIRE(json["astring"] == "");
|
||||||
|
REQUIRE(json["integer"] == 0);
|
||||||
|
REQUIRE(json["adouble"] == 0.0);
|
||||||
|
REQUIRE(json["myList"] == QJsonArray{});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GIVEN("Nested Elements")
|
||||||
|
{
|
||||||
|
WHEN("Can Omit Default Value")
|
||||||
|
{
|
||||||
|
class Parent
|
||||||
|
{
|
||||||
|
class NestedChild
|
||||||
|
{
|
||||||
|
class NestedChild2
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
int childChildInt = 13579;
|
||||||
|
JSONSTRUCT_COMPARE(NestedChild2, childChildInt)
|
||||||
|
JSONSTRUCT_REGISTER(NestedChild2, F(childChildInt))
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
int childInt = 54321;
|
||||||
|
QString childQString = "A QString";
|
||||||
|
NestedChild2 anotherChild;
|
||||||
|
JSONSTRUCT_COMPARE(NestedChild, childInt, childQString, anotherChild)
|
||||||
|
JSONSTRUCT_REGISTER(NestedChild, F(childInt, childQString, anotherChild))
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
int parentInt = 12345;
|
||||||
|
NestedChild child;
|
||||||
|
JSONSTRUCT_REGISTER(Parent, F(parentInt, child))
|
||||||
|
};
|
||||||
|
|
||||||
|
WHEN("Omitted whole child element")
|
||||||
|
{
|
||||||
|
Parent parent;
|
||||||
|
const auto json = parent.toJson();
|
||||||
|
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"] == QJsonValue::Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Omitted one element in the child")
|
||||||
|
{
|
||||||
|
const auto childJson = QJsonObject{ { "childInt", 1314 } };
|
||||||
|
Parent parent;
|
||||||
|
parent.child.childInt = 1314;
|
||||||
|
const auto json = parent.toJson();
|
||||||
|
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"] == childJson);
|
||||||
|
REQUIRE(json["child"]["childInt"] == 1314);
|
||||||
|
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"]["child"]["anotherChild"] == QJsonValue::Undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
WHEN("Omitted one element in the child child")
|
||||||
|
{
|
||||||
|
Parent parent;
|
||||||
|
parent.child.childInt = 1314;
|
||||||
|
parent.child.anotherChild.childChildInt = 97531;
|
||||||
|
const auto json = parent.toJson();
|
||||||
|
REQUIRE(json["parentInt"] == QJsonValue::Undefined);
|
||||||
|
REQUIRE(json["child"]["childInt"] == 1314);
|
||||||
|
REQUIRE(json["child"]["childQString"] == QJsonValue::Undefined);
|
||||||
|
const QJsonObject childChild{ { "childChildInt", 97531 } };
|
||||||
|
REQUIRE(json["child"]["anotherChild"] == childChild);
|
||||||
|
REQUIRE(json["child"]["anotherChild"]["childChildInt"] == 97531);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -72,7 +72,7 @@ namespace QSimpleCrypto
|
|||||||
/// \param notAfter - X509 end date.
|
/// \param notAfter - X509 end date.
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||||
///
|
///
|
||||||
X509* generateSelfSignedCertificate(const RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
X509* generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||||
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
const QByteArray& certificateFileName = "", const EVP_MD* md = EVP_sha512(),
|
||||||
const long& serialNumber = 1, const long& version = x509LastVersion,
|
const long& serialNumber = 1, const long& version = x509LastVersion,
|
||||||
const long& notBefore = 0, const long& notAfter = oneYear);
|
const long& notBefore = 0, const long& notAfter = oneYear);
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ X509* QSimpleCrypto::QX509::verifyCertificate(X509* x509, X509_STORE* store)
|
|||||||
/// \param notAfter - X509 end date.
|
/// \param notAfter - X509 end date.
|
||||||
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
/// \return Returns OpenSSL X509 structure or nullptr, if error happened. Returned value must be cleaned up with 'X509_free' to avoid memory leak.
|
||||||
///
|
///
|
||||||
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(const RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
X509* QSimpleCrypto::QX509::generateSelfSignedCertificate(RSA* rsa, const QMap<QByteArray, QByteArray>& additionalData,
|
||||||
const QByteArray& certificateFileName, const EVP_MD* md,
|
const QByteArray& certificateFileName, const EVP_MD* md,
|
||||||
const long& serialNumber, const long& version,
|
const long& serialNumber, const long& version,
|
||||||
const long& notBefore, const long& notAfter)
|
const long& notBefore, const long& notAfter)
|
||||||
|
|||||||
+35
-6
@@ -63,11 +63,14 @@ qt6_add_resources(QRC ${QRC} ${CMAKE_CURRENT_LIST_DIR}/resources.qrc)
|
|||||||
set(CMAKE_AUTORCC ON)
|
set(CMAKE_AUTORCC ON)
|
||||||
|
|
||||||
set(AMNEZIAVPN_TS_FILES
|
set(AMNEZIAVPN_TS_FILES
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ru_RU.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_zh_CN.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_fa_IR.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar.ts
|
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_ar_EG.ts
|
||||||
${CMAKE_CURRENT_LIST_DIR}/translations/amneziavpn_my_MM.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)
|
file(GLOB_RECURSE AMNEZIAVPN_TS_SOURCES *.qrc *.cpp *.h *.ui)
|
||||||
@@ -119,16 +122,20 @@ set(HEADERS ${HEADERS}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/vpnConfigurationController.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/protocols_defs.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/qml_register_protocols.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/pages.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
|
${CMAKE_CURRENT_LIST_DIR}/ui/property_helper.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.h
|
||||||
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
${CMAKE_CURRENT_BINARY_DIR}/version.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/serialization.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/transfer.h
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla headres
|
# Mozilla headres
|
||||||
@@ -146,11 +153,16 @@ include_directories(mozilla/models)
|
|||||||
|
|
||||||
if(NOT IOS)
|
if(NOT IOS)
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.h
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.h
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
if(NOT ANDROID)
|
||||||
|
set(HEADERS ${HEADERS}
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.h
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
${CMAKE_CURRENT_LIST_DIR}/migrations.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
${CMAKE_CURRENT_LIST_DIR}/amnezia_application.cpp
|
||||||
@@ -158,12 +170,22 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/errorstrings.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/scripts_registry.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/server_defs.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/controllers/apiController.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/controllers/serverController.cpp
|
${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}/protocols/protocols_defs.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/notificationhandler.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
${CMAKE_CURRENT_LIST_DIR}/ui/qautostart.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/vpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
${CMAKE_CURRENT_LIST_DIR}/core/sshclient.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/networkUtilities.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/outbound.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/inbound.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ss.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/ssd.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vless.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/trojan.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/core/serialization/vmess_new.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
# Mozilla sources
|
# Mozilla sources
|
||||||
@@ -180,11 +202,16 @@ endif()
|
|||||||
|
|
||||||
if(NOT IOS)
|
if(NOT IOS)
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/MobileUtils.cpp
|
|
||||||
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
${CMAKE_CURRENT_LIST_DIR}/platforms/ios/QRCodeReaderBase.cpp
|
||||||
)
|
)
|
||||||
endif()
|
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_H CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.h)
|
||||||
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
file(GLOB COMMON_FILES_CPP CONFIGURE_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/*.cpp)
|
||||||
|
|
||||||
@@ -293,6 +320,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.h
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.h
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.h
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -304,6 +332,7 @@ if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))
|
|||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/openvpnovercloakprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/shadowsocksvpnprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/wireguardprotocol.cpp
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/protocols/xrayprotocol.cpp
|
||||||
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
${CMAKE_CURRENT_LIST_DIR}/protocols/awgprotocol.cpp
|
||||||
)
|
)
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@@ -9,14 +9,15 @@
|
|||||||
#include <QTextDocument>
|
#include <QTextDocument>
|
||||||
#include <QTimer>
|
#include <QTimer>
|
||||||
#include <QTranslator>
|
#include <QTranslator>
|
||||||
|
|
||||||
#include <QQuickItem>
|
#include <QQuickItem>
|
||||||
|
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "ui/models/installedAppsModel.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
|
||||||
#include "platforms/ios/QRCodeReaderBase.h"
|
#include "platforms/ios/QRCodeReaderBase.h"
|
||||||
#if defined(Q_OS_ANDROID)
|
#if defined(Q_OS_ANDROID)
|
||||||
|
#include "core/installedAppsImageProvider.h"
|
||||||
#include "platforms/android/android_controller.h"
|
#include "platforms/android/android_controller.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -30,8 +31,8 @@
|
|||||||
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS)
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[]) : AMNEZIA_BASE_CLASS(argc, argv)
|
||||||
#else
|
#else
|
||||||
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options,
|
AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecondary, SingleApplication::Options options, int timeout,
|
||||||
int timeout, const QString &userData)
|
const QString &userData)
|
||||||
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
: SingleApplication(argc, argv, allowSecondary, options, timeout, userData)
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
@@ -44,16 +45,17 @@ AmneziaApplication::AmneziaApplication(int &argc, char *argv[], bool allowSecond
|
|||||||
s.setValue("permFixed", true);
|
s.setValue("permFixed", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
|
QString configLoc1 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||||
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
+ APPLICATION_NAME + ".conf";
|
||||||
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
QFile::setPermissions(configLoc1, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||||
|
|
||||||
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/"
|
QString configLoc2 = QStandardPaths::standardLocations(QStandardPaths::ConfigLocation).first() + "/" + ORGANIZATION_NAME + "/"
|
||||||
+ ORGANIZATION_NAME + "/" + APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
+ APPLICATION_NAME + "/" + APPLICATION_NAME + ".conf";
|
||||||
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
QFile::setPermissions(configLoc2, QFileDevice::ReadOwner | QFileDevice::WriteOwner);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_settings = std::shared_ptr<Settings>(new Settings);
|
m_settings = std::shared_ptr<Settings>(new Settings);
|
||||||
|
m_nam = new QNetworkAccessManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
AmneziaApplication::~AmneziaApplication()
|
AmneziaApplication::~AmneziaApplication()
|
||||||
@@ -82,8 +84,7 @@ void AmneziaApplication::init()
|
|||||||
|
|
||||||
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
m_engine->rootContext()->setContextProperty("Debug", &Logger::Instance());
|
||||||
|
|
||||||
m_configurator = std::shared_ptr<VpnConfigurator>(new VpnConfigurator(m_settings, this));
|
m_vpnConnection.reset(new VpnConnection(m_settings));
|
||||||
m_vpnConnection.reset(new VpnConnection(m_settings, m_configurator));
|
|
||||||
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
m_vpnConnection->moveToThread(&m_vpnConnectionThread);
|
||||||
m_vpnConnectionThread.start();
|
m_vpnConnectionThread.start();
|
||||||
|
|
||||||
@@ -96,25 +97,20 @@ void AmneziaApplication::init()
|
|||||||
qFatal("Android logging initialization failed");
|
qFatal("Android logging initialization failed");
|
||||||
}
|
}
|
||||||
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
AndroidController::instance()->setSaveLogs(m_settings->isSaveLogs());
|
||||||
connect(m_settings.get(), &Settings::saveLogsChanged,
|
connect(m_settings.get(), &Settings::saveLogsChanged, AndroidController::instance(), &AndroidController::setSaveLogs);
|
||||||
AndroidController::instance(), &AndroidController::setSaveLogs);
|
|
||||||
|
|
||||||
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
AndroidController::instance()->setScreenshotsEnabled(m_settings->isScreenshotsEnabled());
|
||||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged,
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
||||||
AndroidController::instance(), &AndroidController::setScreenshotsEnabled);
|
|
||||||
|
|
||||||
connect(m_settings.get(), &Settings::serverRemoved,
|
connect(m_settings.get(), &Settings::serverRemoved, AndroidController::instance(), &AndroidController::resetLastServer);
|
||||||
AndroidController::instance(), &AndroidController::resetLastServer);
|
|
||||||
|
|
||||||
connect(m_settings.get(), &Settings::settingsCleared,
|
connect(m_settings.get(), &Settings::settingsCleared, []() { AndroidController::instance()->resetLastServer(-1); });
|
||||||
[](){ AndroidController::instance()->resetLastServer(-1); });
|
|
||||||
|
|
||||||
connect(AndroidController::instance(), &AndroidController::initConnectionState, this,
|
connect(AndroidController::instance(), &AndroidController::initConnectionState, this, [this](Vpn::ConnectionState state) {
|
||||||
[this](Vpn::ConnectionState state) {
|
m_connectionController->onConnectionStateChanged(state);
|
||||||
m_connectionController->onConnectionStateChanged(state);
|
if (m_vpnConnection)
|
||||||
if (m_vpnConnection)
|
m_vpnConnection->restoreConnection();
|
||||||
m_vpnConnection->restoreConnection();
|
});
|
||||||
});
|
|
||||||
if (!AndroidController::instance()->initialize()) {
|
if (!AndroidController::instance()->initialize()) {
|
||||||
qFatal("Android controller initialization failed");
|
qFatal("Android controller initialization failed");
|
||||||
}
|
}
|
||||||
@@ -124,6 +120,8 @@ void AmneziaApplication::init()
|
|||||||
m_importController->extractConfigFromData(data);
|
m_importController->extractConfigFromData(data);
|
||||||
m_pageController->goToPageViewConfig();
|
m_pageController->goToPageViewConfig();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_engine->addImageProvider(QLatin1String("installedAppImage"), new InstalledAppsImageProvider);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef Q_OS_IOS
|
#ifdef Q_OS_IOS
|
||||||
@@ -140,28 +138,24 @@ void AmneziaApplication::init()
|
|||||||
m_settingsController->importBackupFromOutside(filePath);
|
m_settingsController->importBackupFromOutside(filePath);
|
||||||
});
|
});
|
||||||
|
|
||||||
QTimer::singleShot(0, this, [this](){
|
QTimer::singleShot(0, this, [this]() { AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled()); });
|
||||||
AmneziaVPN::toggleScreenshots(m_settings->isScreenshotsEnabled());
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) {
|
connect(m_settings.get(), &Settings::screenshotsEnabledChanged, [](bool enabled) { AmneziaVPN::toggleScreenshots(enabled); });
|
||||||
AmneziaVPN::toggleScreenshots(enabled);
|
|
||||||
});
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
m_notificationHandler.reset(NotificationHandler::create(nullptr));
|
||||||
|
|
||||||
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
connect(m_vpnConnection.get(), &VpnConnection::connectionStateChanged, m_notificationHandler.get(),
|
||||||
&NotificationHandler::setConnectionState);
|
&NotificationHandler::setConnectionState);
|
||||||
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::raiseRequested, m_pageController.get(), &PageController::raiseMainWindow);
|
||||||
&PageController::raiseMainWindow);
|
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::connectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::openConnection);
|
static_cast<void (ConnectionController::*)()>(&ConnectionController::openConnection));
|
||||||
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
connect(m_notificationHandler.get(), &NotificationHandler::disconnectRequested, m_connectionController.get(),
|
||||||
&ConnectionController::closeConnection);
|
&ConnectionController::closeConnection);
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(),
|
connect(this, &AmneziaApplication::translationsUpdated, m_notificationHandler.get(), &NotificationHandler::onTranslationsUpdated);
|
||||||
&NotificationHandler::onTranslationsUpdated);
|
#endif
|
||||||
|
|
||||||
m_engine->load(url);
|
m_engine->load(url);
|
||||||
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
m_systemController->setQmlRoot(m_engine->rootObjects().value(0));
|
||||||
@@ -234,7 +228,8 @@ void AmneziaApplication::registerTypes()
|
|||||||
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
qmlRegisterSingletonType(QUrl("qrc:/ui/qml/Filters/ContainersModelFilters.qml"), "ContainersModelFilters", 1, 0,
|
||||||
"ContainersModelFilters");
|
"ContainersModelFilters");
|
||||||
|
|
||||||
//
|
qmlRegisterType<InstalledAppsModel>("InstalledAppsModel", 1, 0, "InstalledAppsModel");
|
||||||
|
|
||||||
Vpn::declareQmlVpnConnectionStateEnum();
|
Vpn::declareQmlVpnConnectionStateEnum();
|
||||||
PageLoader::declareQmlPageEnum();
|
PageLoader::declareQmlPageEnum();
|
||||||
}
|
}
|
||||||
@@ -311,8 +306,7 @@ void AmneziaApplication::initModels()
|
|||||||
|
|
||||||
m_serversModel.reset(new ServersModel(m_settings, this));
|
m_serversModel.reset(new ServersModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
m_engine->rootContext()->setContextProperty("ServersModel", m_serversModel.get());
|
||||||
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::containersUpdated, m_containersModel.get(), &ContainersModel::updateModel);
|
||||||
&ContainersModel::updateModel);
|
|
||||||
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
connect(m_serversModel.get(), &ServersModel::defaultServerContainersUpdated, m_defaultServerContainersModel.get(),
|
||||||
&ContainersModel::updateModel);
|
&ContainersModel::updateModel);
|
||||||
m_serversModel->resetModel();
|
m_serversModel->resetModel();
|
||||||
@@ -325,6 +319,9 @@ void AmneziaApplication::initModels()
|
|||||||
m_sitesModel.reset(new SitesModel(m_settings, this));
|
m_sitesModel.reset(new SitesModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
m_engine->rootContext()->setContextProperty("SitesModel", m_sitesModel.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingModel.reset(new AppSplitTunnelingModel(m_settings, this));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingModel", m_appSplitTunnelingModel.get());
|
||||||
|
|
||||||
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
m_protocolsModel.reset(new ProtocolsModel(m_settings, this));
|
||||||
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
m_engine->rootContext()->setContextProperty("ProtocolsModel", m_protocolsModel.get());
|
||||||
|
|
||||||
@@ -343,6 +340,9 @@ void AmneziaApplication::initModels()
|
|||||||
m_awgConfigModel.reset(new AwgConfigModel(this));
|
m_awgConfigModel.reset(new AwgConfigModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
m_engine->rootContext()->setContextProperty("AwgConfigModel", m_awgConfigModel.get());
|
||||||
|
|
||||||
|
m_xrayConfigModel.reset(new XrayConfigModel(this));
|
||||||
|
m_engine->rootContext()->setContextProperty("XrayConfigModel", m_xrayConfigModel.get());
|
||||||
|
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
m_ikev2ConfigModel.reset(new Ikev2ConfigModel(this));
|
||||||
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
m_engine->rootContext()->setContextProperty("Ikev2ConfigModel", m_ikev2ConfigModel.get());
|
||||||
@@ -355,28 +355,33 @@ void AmneziaApplication::initModels()
|
|||||||
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
m_engine->rootContext()->setContextProperty("ClientManagementModel", m_clientManagementModel.get());
|
||||||
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
connect(m_clientManagementModel.get(), &ClientManagementModel::adminConfigRevoked, m_serversModel.get(),
|
||||||
&ServersModel::clearCachedProfile);
|
&ServersModel::clearCachedProfile);
|
||||||
|
|
||||||
connect(m_configurator.get(), &VpnConfigurator::newVpnConfigCreated, this,
|
|
||||||
[this](const QString &clientId, const QString &clientName, const DockerContainer container,
|
|
||||||
ServerCredentials credentials) {
|
|
||||||
m_serversModel->reloadDefaultServerContainerConfig();
|
|
||||||
m_clientManagementModel->appendClient(clientId, clientName, container, credentials);
|
|
||||||
emit m_configurator->clientModelUpdated();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AmneziaApplication::initControllers()
|
void AmneziaApplication::initControllers()
|
||||||
{
|
{
|
||||||
m_connectionController.reset(new ConnectionController(m_serversModel, m_containersModel, m_vpnConnection));
|
m_connectionController.reset(
|
||||||
|
new ConnectionController(m_serversModel, m_containersModel, m_clientManagementModel, m_vpnConnection, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
m_engine->rootContext()->setContextProperty("ConnectionController", m_connectionController.get());
|
||||||
|
|
||||||
connect(this, &AmneziaApplication::translationsUpdated, m_connectionController.get(),
|
connect(m_connectionController.get(), qOverload<const QString &>(&ConnectionController::connectionErrorOccurred), this, [this](const QString &errorMessage) {
|
||||||
&ConnectionController::onTranslationsUpdated);
|
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_pageController.reset(new PageController(m_serversModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
m_engine->rootContext()->setContextProperty("PageController", m_pageController.get());
|
||||||
|
|
||||||
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_settings));
|
m_installController.reset(new InstallController(m_serversModel, m_containersModel, m_protocolsModel, m_clientManagementModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
m_engine->rootContext()->setContextProperty("InstallController", m_installController.get());
|
||||||
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
connect(m_installController.get(), &InstallController::passphraseRequestStarted, m_pageController.get(),
|
||||||
&PageController::showPassphraseRequestDrawer);
|
&PageController::showPassphraseRequestDrawer);
|
||||||
@@ -388,36 +393,23 @@ void AmneziaApplication::initControllers()
|
|||||||
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
m_importController.reset(new ImportController(m_serversModel, m_containersModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
m_engine->rootContext()->setContextProperty("ImportController", m_importController.get());
|
||||||
|
|
||||||
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel,
|
m_exportController.reset(new ExportController(m_serversModel, m_containersModel, m_clientManagementModel, m_settings));
|
||||||
m_settings, m_configurator));
|
|
||||||
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
m_engine->rootContext()->setContextProperty("ExportController", m_exportController.get());
|
||||||
|
|
||||||
m_settingsController.reset(
|
m_settingsController.reset(
|
||||||
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_settings));
|
new SettingsController(m_serversModel, m_containersModel, m_languageModel, m_sitesModel, m_appSplitTunnelingModel, m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
m_engine->rootContext()->setContextProperty("SettingsController", m_settingsController.get());
|
||||||
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
if (m_settingsController->isAutoConnectEnabled() && m_serversModel->getDefaultServerIndex() >= 0) {
|
||||||
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
QTimer::singleShot(1000, this, [this]() { m_connectionController->openConnection(); });
|
||||||
}
|
}
|
||||||
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(),
|
connect(m_settingsController.get(), &SettingsController::amneziaDnsToggled, m_serversModel.get(), &ServersModel::toggleAmneziaDns);
|
||||||
&ServersModel::toggleAmneziaDns);
|
|
||||||
|
|
||||||
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
m_sitesController.reset(new SitesController(m_settings, m_vpnConnection, m_sitesModel));
|
||||||
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
m_engine->rootContext()->setContextProperty("SitesController", m_sitesController.get());
|
||||||
|
|
||||||
|
m_appSplitTunnelingController.reset(new AppSplitTunnelingController(m_settings, m_appSplitTunnelingModel));
|
||||||
|
m_engine->rootContext()->setContextProperty("AppSplitTunnelingController", m_appSplitTunnelingController.get());
|
||||||
|
|
||||||
m_systemController.reset(new SystemController(m_settings));
|
m_systemController.reset(new SystemController(m_settings));
|
||||||
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
m_engine->rootContext()->setContextProperty("SystemController", m_systemController.get());
|
||||||
|
|
||||||
m_apiController.reset(new ApiController(m_serversModel, m_containersModel));
|
|
||||||
m_engine->rootContext()->setContextProperty("ApiController", m_apiController.get());
|
|
||||||
connect(m_apiController.get(), &ApiController::updateStarted, this,
|
|
||||||
[this]() { emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Connecting); });
|
|
||||||
connect(m_apiController.get(), &ApiController::errorOccurred, this, [this](const QString &errorMessage) {
|
|
||||||
if (m_connectionController->isConnectionInProgress()) {
|
|
||||||
emit m_pageController->showErrorMessage(errorMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
emit m_vpnConnection->connectionStateChanged(Vpn::ConnectionState::Disconnected);
|
|
||||||
});
|
|
||||||
connect(m_apiController.get(), &ApiController::updateFinished, m_connectionController.get(),
|
|
||||||
&ConnectionController::toggleConnection);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#define AMNEZIA_APPLICATION_H
|
#define AMNEZIA_APPLICATION_H
|
||||||
|
|
||||||
#include <QCommandLineParser>
|
#include <QCommandLineParser>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
#include <QThread>
|
#include <QThread>
|
||||||
@@ -14,8 +15,6 @@
|
|||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "vpnconnection.h"
|
#include "vpnconnection.h"
|
||||||
|
|
||||||
#include "configurators/vpn_configurator.h"
|
|
||||||
|
|
||||||
#include "ui/controllers/connectionController.h"
|
#include "ui/controllers/connectionController.h"
|
||||||
#include "ui/controllers/exportController.h"
|
#include "ui/controllers/exportController.h"
|
||||||
#include "ui/controllers/importController.h"
|
#include "ui/controllers/importController.h"
|
||||||
@@ -24,11 +23,13 @@
|
|||||||
#include "ui/controllers/settingsController.h"
|
#include "ui/controllers/settingsController.h"
|
||||||
#include "ui/controllers/sitesController.h"
|
#include "ui/controllers/sitesController.h"
|
||||||
#include "ui/controllers/systemController.h"
|
#include "ui/controllers/systemController.h"
|
||||||
#include "ui/controllers/apiController.h"
|
#include "ui/controllers/appSplitTunnelingController.h"
|
||||||
#include "ui/models/containers_model.h"
|
#include "ui/models/containers_model.h"
|
||||||
#include "ui/models/languageModel.h"
|
#include "ui/models/languageModel.h"
|
||||||
#include "ui/models/protocols/cloakConfigModel.h"
|
#include "ui/models/protocols/cloakConfigModel.h"
|
||||||
#include "ui/notificationhandler.h"
|
#ifndef Q_OS_ANDROID
|
||||||
|
#include "ui/notificationhandler.h"
|
||||||
|
#endif
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
#include "ui/models/protocols/ikev2ConfigModel.h"
|
#include "ui/models/protocols/ikev2ConfigModel.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -36,11 +37,13 @@
|
|||||||
#include "ui/models/protocols/openvpnConfigModel.h"
|
#include "ui/models/protocols/openvpnConfigModel.h"
|
||||||
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
#include "ui/models/protocols/shadowsocksConfigModel.h"
|
||||||
#include "ui/models/protocols/wireguardConfigModel.h"
|
#include "ui/models/protocols/wireguardConfigModel.h"
|
||||||
|
#include "ui/models/protocols/xrayConfigModel.h"
|
||||||
#include "ui/models/protocols_model.h"
|
#include "ui/models/protocols_model.h"
|
||||||
#include "ui/models/servers_model.h"
|
#include "ui/models/servers_model.h"
|
||||||
#include "ui/models/services/sftpConfigModel.h"
|
#include "ui/models/services/sftpConfigModel.h"
|
||||||
#include "ui/models/sites_model.h"
|
#include "ui/models/sites_model.h"
|
||||||
#include "ui/models/clientManagementModel.h"
|
#include "ui/models/clientManagementModel.h"
|
||||||
|
#include "ui/models/appSplitTunnelingModel.h"
|
||||||
|
|
||||||
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
#define amnApp (static_cast<AmneziaApplication *>(QCoreApplication::instance()))
|
||||||
|
|
||||||
@@ -73,6 +76,7 @@ public:
|
|||||||
bool parseCommands();
|
bool parseCommands();
|
||||||
|
|
||||||
QQmlApplicationEngine *qmlEngine() const;
|
QQmlApplicationEngine *qmlEngine() const;
|
||||||
|
QNetworkAccessManager *manager() { return m_nam; }
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void translationsUpdated();
|
void translationsUpdated();
|
||||||
@@ -83,7 +87,6 @@ private:
|
|||||||
|
|
||||||
QQmlApplicationEngine *m_engine {};
|
QQmlApplicationEngine *m_engine {};
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
std::shared_ptr<VpnConfigurator> m_configurator;
|
|
||||||
|
|
||||||
QSharedPointer<ContainerProps> m_containerProps;
|
QSharedPointer<ContainerProps> m_containerProps;
|
||||||
QSharedPointer<ProtocolProps> m_protocolProps;
|
QSharedPointer<ProtocolProps> m_protocolProps;
|
||||||
@@ -97,11 +100,13 @@ private:
|
|||||||
QSharedPointer<LanguageModel> m_languageModel;
|
QSharedPointer<LanguageModel> m_languageModel;
|
||||||
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
QSharedPointer<ProtocolsModel> m_protocolsModel;
|
||||||
QSharedPointer<SitesModel> m_sitesModel;
|
QSharedPointer<SitesModel> m_sitesModel;
|
||||||
|
QSharedPointer<AppSplitTunnelingModel> m_appSplitTunnelingModel;
|
||||||
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
QSharedPointer<ClientManagementModel> m_clientManagementModel;
|
||||||
|
|
||||||
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
QScopedPointer<OpenVpnConfigModel> m_openVpnConfigModel;
|
||||||
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
QScopedPointer<ShadowSocksConfigModel> m_shadowSocksConfigModel;
|
||||||
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
QScopedPointer<CloakConfigModel> m_cloakConfigModel;
|
||||||
|
QScopedPointer<XrayConfigModel> m_xrayConfigModel;
|
||||||
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
QScopedPointer<WireGuardConfigModel> m_wireGuardConfigModel;
|
||||||
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
QScopedPointer<AwgConfigModel> m_awgConfigModel;
|
||||||
#ifdef Q_OS_WINDOWS
|
#ifdef Q_OS_WINDOWS
|
||||||
@@ -112,7 +117,9 @@ private:
|
|||||||
|
|
||||||
QSharedPointer<VpnConnection> m_vpnConnection;
|
QSharedPointer<VpnConnection> m_vpnConnection;
|
||||||
QThread m_vpnConnectionThread;
|
QThread m_vpnConnectionThread;
|
||||||
|
#ifndef Q_OS_ANDROID
|
||||||
QScopedPointer<NotificationHandler> m_notificationHandler;
|
QScopedPointer<NotificationHandler> m_notificationHandler;
|
||||||
|
#endif
|
||||||
|
|
||||||
QScopedPointer<ConnectionController> m_connectionController;
|
QScopedPointer<ConnectionController> m_connectionController;
|
||||||
QScopedPointer<PageController> m_pageController;
|
QScopedPointer<PageController> m_pageController;
|
||||||
@@ -122,7 +129,9 @@ private:
|
|||||||
QScopedPointer<SettingsController> m_settingsController;
|
QScopedPointer<SettingsController> m_settingsController;
|
||||||
QScopedPointer<SitesController> m_sitesController;
|
QScopedPointer<SitesController> m_sitesController;
|
||||||
QScopedPointer<SystemController> m_systemController;
|
QScopedPointer<SystemController> m_systemController;
|
||||||
QScopedPointer<ApiController> m_apiController;
|
QScopedPointer<AppSplitTunnelingController> m_appSplitTunnelingController;
|
||||||
|
|
||||||
|
QNetworkAccessManager *m_nam;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AMNEZIA_APPLICATION_H
|
#endif // AMNEZIA_APPLICATION_H
|
||||||
|
|||||||
@@ -24,9 +24,7 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SYSTEM_EXEMPTED" />
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
|
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:ignore="QueryAllPackagesPermission" />
|
||||||
<!-- Enable when VPN-per-app mode will be implemented -->
|
|
||||||
<!-- <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/> -->
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:name=".AmneziaApplication"
|
android:name=".AmneziaApplication"
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ class Awg : Wireguard() {
|
|||||||
return AwgConfig.build {
|
return AwgConfig.build {
|
||||||
configWireguard(configData, configDataJson)
|
configWireguard(configData, configDataJson)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
|
configAppSplitTunneling(config)
|
||||||
configData["Jc"]?.let { setJc(it.toInt()) }
|
configData["Jc"]?.let { setJc(it.toInt()) }
|
||||||
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
configData["Jmin"]?.let { setJmin(it.toInt()) }
|
||||||
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
configData["Jmax"]?.let { setJmax(it.toInt()) }
|
||||||
|
|||||||
@@ -3,9 +3,6 @@ package org.amnezia.vpn.protocol.cloak
|
|||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import net.openvpn.ovpn3.ClientAPI_Config
|
import net.openvpn.ovpn3.ClientAPI_Config
|
||||||
import org.amnezia.vpn.protocol.openvpn.OpenVpn
|
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
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -54,13 +51,6 @@ class Cloak : OpenVpn() {
|
|||||||
return openVpnConfig
|
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 {
|
private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject {
|
||||||
cloakConfigJson.put("NumConn", 1)
|
cloakConfigJson.put("NumConn", 1)
|
||||||
cloakConfigJson.put("ProxyMethod", "openvpn")
|
cloakConfigJson.put("ProxyMethod", "openvpn")
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import org.amnezia.vpn.protocol.ProtocolState
|
|||||||
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTED
|
||||||
import org.amnezia.vpn.protocol.Statistics
|
import org.amnezia.vpn.protocol.Statistics
|
||||||
import org.amnezia.vpn.protocol.VpnStartException
|
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.getLocalNetworks
|
||||||
|
import org.amnezia.vpn.util.net.parseInetAddress
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,8 +79,15 @@ open class OpenVpn : Protocol() {
|
|||||||
if (evalConfig.error) {
|
if (evalConfig.error) {
|
||||||
throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}")
|
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)
|
configPluggableTransport(configBuilder, config)
|
||||||
configBuilder.configSplitTunneling(config)
|
configBuilder.configSplitTunneling(config)
|
||||||
|
configBuilder.configAppSplitTunneling(config)
|
||||||
|
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val status = client.connect()
|
val status = client.connect()
|
||||||
|
|||||||
@@ -64,6 +64,22 @@ abstract class Protocol {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun ProtocolConfig.Builder.configAppSplitTunneling(config: JSONObject) {
|
||||||
|
val splitTunnelType = config.optInt("appSplitTunnelType")
|
||||||
|
if (splitTunnelType == SPLIT_TUNNEL_DISABLE) return
|
||||||
|
val splitTunnelApps = config.getJSONArray("splitTunnelApps")
|
||||||
|
val appHandlerFunc = when (splitTunnelType) {
|
||||||
|
SPLIT_TUNNEL_INCLUDE -> ::includeApplication
|
||||||
|
SPLIT_TUNNEL_EXCLUDE -> ::excludeApplication
|
||||||
|
|
||||||
|
else -> throw BadConfigException("Unexpected value of the 'appSplitTunnelType' parameter: $splitTunnelType")
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in 0 until splitTunnelApps.length()) {
|
||||||
|
appHandlerFunc(splitTunnelApps.getString(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
|
protected open fun buildVpnInterface(config: ProtocolConfig, vpnBuilder: Builder) {
|
||||||
vpnBuilder.setSession(VPN_SESSION_NAME)
|
vpnBuilder.setSession(VPN_SESSION_NAME)
|
||||||
|
|
||||||
@@ -89,20 +105,27 @@ abstract class Protocol {
|
|||||||
vpnBuilder.addSearchDomain(it)
|
vpnBuilder.addSearchDomain(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (addr in config.routes) {
|
for ((inetNetwork, include) in config.routes) {
|
||||||
Log.d(TAG, "addRoute: $addr")
|
if (include) {
|
||||||
vpnBuilder.addRoute(addr)
|
Log.d(TAG, "addRoute: $inetNetwork")
|
||||||
}
|
vpnBuilder.addRoute(inetNetwork)
|
||||||
|
} else {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||||
for (addr in config.excludedRoutes) {
|
Log.d(TAG, "excludeRoute: $inetNetwork")
|
||||||
Log.d(TAG, "excludeRoute: $addr")
|
vpnBuilder.excludeRoute(inetNetwork)
|
||||||
vpnBuilder.excludeRoute(addr)
|
} else {
|
||||||
|
Log.e(TAG, "Trying to exclude route $inetNetwork on old Android")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (app in config.includedApplications) {
|
||||||
|
Log.d(TAG, "addAllowedApplication")
|
||||||
|
vpnBuilder.addAllowedApplication(app)
|
||||||
|
}
|
||||||
|
|
||||||
for (app in config.excludedApplications) {
|
for (app in config.excludedApplications) {
|
||||||
Log.d(TAG, "addDisallowedApplication: $app")
|
Log.d(TAG, "addDisallowedApplication")
|
||||||
vpnBuilder.addDisallowedApplication(app)
|
vpnBuilder.addDisallowedApplication(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ open class ProtocolConfig protected constructor(
|
|||||||
val addresses: Set<InetNetwork>,
|
val addresses: Set<InetNetwork>,
|
||||||
val dnsServers: Set<InetAddress>,
|
val dnsServers: Set<InetAddress>,
|
||||||
val searchDomain: String?,
|
val searchDomain: String?,
|
||||||
val routes: Set<InetNetwork>,
|
val routes: Set<Route>,
|
||||||
val excludedRoutes: Set<InetNetwork>,
|
|
||||||
val includedAddresses: Set<InetNetwork>,
|
val includedAddresses: Set<InetNetwork>,
|
||||||
val excludedAddresses: Set<InetNetwork>,
|
val excludedAddresses: Set<InetNetwork>,
|
||||||
|
val includedApplications: Set<String>,
|
||||||
val excludedApplications: Set<String>,
|
val excludedApplications: Set<String>,
|
||||||
val httpProxy: ProxyInfo?,
|
val httpProxy: ProxyInfo?,
|
||||||
val allowAllAF: Boolean,
|
val allowAllAF: Boolean,
|
||||||
@@ -28,9 +28,9 @@ open class ProtocolConfig protected constructor(
|
|||||||
builder.dnsServers,
|
builder.dnsServers,
|
||||||
builder.searchDomain,
|
builder.searchDomain,
|
||||||
builder.routes,
|
builder.routes,
|
||||||
builder.excludedRoutes,
|
|
||||||
builder.includedAddresses,
|
builder.includedAddresses,
|
||||||
builder.excludedAddresses,
|
builder.excludedAddresses,
|
||||||
|
builder.includedApplications,
|
||||||
builder.excludedApplications,
|
builder.excludedApplications,
|
||||||
builder.httpProxy,
|
builder.httpProxy,
|
||||||
builder.allowAllAF,
|
builder.allowAllAF,
|
||||||
@@ -41,10 +41,10 @@ open class ProtocolConfig protected constructor(
|
|||||||
open class Builder(blockingMode: Boolean) {
|
open class Builder(blockingMode: Boolean) {
|
||||||
internal val addresses: MutableSet<InetNetwork> = hashSetOf()
|
internal val addresses: MutableSet<InetNetwork> = hashSetOf()
|
||||||
internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
|
internal val dnsServers: MutableSet<InetAddress> = hashSetOf()
|
||||||
internal val routes: MutableSet<InetNetwork> = hashSetOf()
|
internal val routes: MutableSet<Route> = mutableSetOf()
|
||||||
internal val excludedRoutes: MutableSet<InetNetwork> = hashSetOf()
|
|
||||||
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
internal val includedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
||||||
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
internal val excludedAddresses: MutableSet<InetNetwork> = hashSetOf()
|
||||||
|
internal val includedApplications: MutableSet<String> = hashSetOf()
|
||||||
internal val excludedApplications: MutableSet<String> = hashSetOf()
|
internal val excludedApplications: MutableSet<String> = hashSetOf()
|
||||||
|
|
||||||
internal var searchDomain: String? = null
|
internal var searchDomain: String? = null
|
||||||
@@ -74,13 +74,21 @@ open class ProtocolConfig protected constructor(
|
|||||||
|
|
||||||
fun setSearchDomain(domain: String) = apply { this.searchDomain = domain }
|
fun setSearchDomain(domain: String) = apply { this.searchDomain = domain }
|
||||||
|
|
||||||
fun addRoute(route: InetNetwork) = apply { this.routes += route }
|
fun addRoute(route: InetNetwork) = apply { this.routes += Route(route, true) }
|
||||||
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes }
|
fun addRoutes(routes: Collection<InetNetwork>) = apply { this.routes += routes.map { Route(it, true) } }
|
||||||
fun removeRoute(route: InetNetwork) = apply { this.routes.remove(route) }
|
|
||||||
|
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 clearRoutes() = apply { this.routes.clear() }
|
fun clearRoutes() = apply { this.routes.clear() }
|
||||||
|
|
||||||
fun excludeRoute(route: InetNetwork) = apply { this.excludedRoutes += route }
|
fun prependRoutes(block: Builder.() -> Unit) = apply {
|
||||||
fun excludeRoutes(routes: Collection<InetNetwork>) = apply { this.excludedRoutes += routes }
|
val savedRoutes = mutableListOf<Route>().apply { addAll(routes) }
|
||||||
|
routes.clear()
|
||||||
|
block()
|
||||||
|
routes.addAll(savedRoutes)
|
||||||
|
}
|
||||||
|
|
||||||
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
|
fun includeAddress(addr: InetNetwork) = apply { this.includedAddresses += addr }
|
||||||
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
|
fun includeAddresses(addresses: Collection<InetNetwork>) = apply { this.includedAddresses += addresses }
|
||||||
@@ -88,6 +96,9 @@ open class ProtocolConfig protected constructor(
|
|||||||
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
|
fun excludeAddress(addr: InetNetwork) = apply { this.excludedAddresses += addr }
|
||||||
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
|
fun excludeAddresses(addresses: Collection<InetNetwork>) = apply { this.excludedAddresses += addresses }
|
||||||
|
|
||||||
|
fun includeApplication(application: String) = apply { this.includedApplications += application }
|
||||||
|
fun includeApplications(applications: Collection<String>) = apply { this.includedApplications += applications }
|
||||||
|
|
||||||
fun excludeApplication(application: String) = apply { this.excludedApplications += application }
|
fun excludeApplication(application: String) = apply { this.excludedApplications += application }
|
||||||
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
|
fun excludeApplications(applications: Collection<String>) = apply { this.excludedApplications += applications }
|
||||||
|
|
||||||
@@ -111,37 +122,46 @@ open class ProtocolConfig protected constructor(
|
|||||||
// remove default routes, if any
|
// remove default routes, if any
|
||||||
removeRoute(InetNetwork("0.0.0.0", 0))
|
removeRoute(InetNetwork("0.0.0.0", 0))
|
||||||
removeRoute(InetNetwork("::", 0))
|
removeRoute(InetNetwork("::", 0))
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
removeRoute(InetNetwork("2000::", 3))
|
||||||
// for older versions of Android, add the default route to the excluded routes
|
prependRoutes {
|
||||||
// to correctly build the excluded subnets list later
|
addRoutes(includedAddresses)
|
||||||
excludeRoute(InetNetwork("0.0.0.0", 0))
|
|
||||||
}
|
}
|
||||||
addRoutes(includedAddresses)
|
|
||||||
} else if (excludedAddresses.isNotEmpty()) {
|
} else if (excludedAddresses.isNotEmpty()) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
prependRoutes {
|
||||||
// default routes are required for split tunneling in newer versions of Android
|
|
||||||
addRoute(InetNetwork("0.0.0.0", 0))
|
addRoute(InetNetwork("0.0.0.0", 0))
|
||||||
addRoute(InetNetwork("::", 0))
|
addRoute(InetNetwork("2000::", 3))
|
||||||
|
excludeRoutes(excludedAddresses)
|
||||||
}
|
}
|
||||||
excludeRoutes(excludedAddresses)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun processExcludedRoutes() {
|
private fun processRoutes() {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && excludedRoutes.isNotEmpty()) {
|
// replace ::/0 as it may cause LAN connection issues
|
||||||
// todo: rewrite, taking into account the current routes
|
val ipv6DefaultRoute = InetNetwork("::", 0)
|
||||||
// for older versions of Android, build a list of subnets without excluded routes
|
if (routes.removeIf { it.include && it.inetNetwork == ipv6DefaultRoute }) {
|
||||||
// and add them to routes
|
prependRoutes {
|
||||||
val ipRangeSet = IpRangeSet()
|
addRoute(InetNetwork("2000::", 3))
|
||||||
ipRangeSet.remove(IpRange("127.0.0.0", 8))
|
|
||||||
excludedRoutes.forEach {
|
|
||||||
ipRangeSet.remove(IpRange(it))
|
|
||||||
}
|
}
|
||||||
// remove default routes, if any
|
}
|
||||||
removeRoute(InetNetwork("0.0.0.0", 0))
|
// for older versions of Android, build a list of subnets without excluded routes
|
||||||
removeRoute(InetNetwork("::", 0))
|
// and add them to routes
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU && routes.any { !it.include }) {
|
||||||
|
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()
|
||||||
ipRangeSet.subnets().forEach(::addRoute)
|
ipRangeSet.subnets().forEach(::addRoute)
|
||||||
addRoute(InetNetwork("2000::", 3))
|
}
|
||||||
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +179,7 @@ open class ProtocolConfig protected constructor(
|
|||||||
|
|
||||||
protected fun configBuild() {
|
protected fun configBuild() {
|
||||||
processSplitTunneling()
|
processSplitTunneling()
|
||||||
processExcludedRoutes()
|
processRoutes()
|
||||||
validate()
|
validate()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,3 +191,5 @@ open class ProtocolConfig protected constructor(
|
|||||||
Builder(blockingMode).apply(block).build()
|
Builder(blockingMode).apply(block).build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Route(val inetNetwork: InetNetwork, val include: Boolean)
|
||||||
|
|||||||
@@ -1,5 +1,26 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="connecting">Подключение</string>
|
<string name="disconnected">Не подключено</string>
|
||||||
<string name="disconnecting">Отключение</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="cancel">Отмена</string>
|
||||||
|
<string name="yes">Да</string>
|
||||||
|
<string name="no">Нет</string>
|
||||||
|
|
||||||
|
<string name="vpnGranted">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>
|
</resources>
|
||||||
@@ -1,5 +1,26 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="connecting">Connecting</string>
|
<string name="disconnected">Not connected</string>
|
||||||
<string name="disconnecting">Disconnecting</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="cancel">Cancel</string>
|
||||||
|
<string name="yes">Yes</string>
|
||||||
|
<string name="no">No</string>
|
||||||
|
|
||||||
|
<string name="vpnGranted">VPN permission granted</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>
|
</resources>
|
||||||
@@ -1,37 +1,48 @@
|
|||||||
package org.amnezia.vpn
|
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.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.Intent.EXTRA_MIME_TYPES
|
import android.content.Intent.EXTRA_MIME_TYPES
|
||||||
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
import android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
|
||||||
import android.content.ServiceConnection
|
import android.content.ServiceConnection
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.graphics.Bitmap
|
||||||
import android.net.VpnService
|
import android.net.VpnService
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
import android.os.Messenger
|
import android.os.Messenger
|
||||||
|
import android.provider.Settings
|
||||||
import android.view.WindowManager.LayoutParams
|
import android.view.WindowManager.LayoutParams
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlin.text.RegexOption.IGNORE_CASE
|
import kotlin.text.RegexOption.IGNORE_CASE
|
||||||
|
import AppListProvider
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import org.amnezia.vpn.protocol.getStatistics
|
import org.amnezia.vpn.protocol.getStatistics
|
||||||
import org.amnezia.vpn.protocol.getStatus
|
import org.amnezia.vpn.protocol.getStatus
|
||||||
import org.amnezia.vpn.qt.QtAndroidController
|
import org.amnezia.vpn.qt.QtAndroidController
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.qtproject.qt.android.bindings.QtActivity
|
import org.qtproject.qt.android.bindings.QtActivity
|
||||||
|
|
||||||
private const val TAG = "AmneziaActivity"
|
private const val TAG = "AmneziaActivity"
|
||||||
@@ -40,6 +51,9 @@ const val ACTIVITY_MESSENGER_NAME = "Activity"
|
|||||||
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
|
private const val CHECK_VPN_PERMISSION_ACTION_CODE = 1
|
||||||
private const val CREATE_FILE_ACTION_CODE = 2
|
private const val CREATE_FILE_ACTION_CODE = 2
|
||||||
private const val OPEN_FILE_ACTION_CODE = 3
|
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() {
|
class AmneziaActivity : QtActivity() {
|
||||||
|
|
||||||
@@ -48,8 +62,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
private var isWaitingStatus = true
|
private var isWaitingStatus = true
|
||||||
private var isServiceConnected = false
|
private var isServiceConnected = false
|
||||||
private var isInBoundState = false
|
private var isInBoundState = false
|
||||||
|
private var notificationStateReceiver: BroadcastReceiver? = null
|
||||||
private lateinit var vpnServiceMessenger: IpcMessenger
|
private lateinit var vpnServiceMessenger: IpcMessenger
|
||||||
private var tmpFileContentToSave: String = ""
|
|
||||||
|
private val actionResultHandlers = mutableMapOf<Int, ActivityResultHandler>()
|
||||||
|
private val permissionRequestHandlers = mutableMapOf<Int, PermissionRequestHandler>()
|
||||||
|
|
||||||
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
private val vpnServiceEventHandler: Handler by lazy(NONE) {
|
||||||
object : Handler(Looper.getMainLooper()) {
|
object : Handler(Looper.getMainLooper()) {
|
||||||
@@ -129,10 +146,6 @@ class AmneziaActivity : QtActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class CheckVpnPermissionCallbacks(val onSuccess: () -> Unit, val onFail: () -> Unit)
|
|
||||||
|
|
||||||
private var checkVpnPermissionCallbacks: CheckVpnPermissionCallbacks? = null
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity overloaded methods
|
* Activity overloaded methods
|
||||||
*/
|
*/
|
||||||
@@ -147,9 +160,30 @@ class AmneziaActivity : QtActivity() {
|
|||||||
doBindService()
|
doBindService()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
registerBroadcastReceivers()
|
||||||
intent?.let(::processIntent)
|
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?) {
|
override fun onNewIntent(intent: Intent?) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
Log.d(TAG, "onNewIntent: $intent")
|
Log.d(TAG, "onNewIntent: $intent")
|
||||||
@@ -187,50 +221,46 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Destroy Amnezia activity")
|
Log.d(TAG, "Destroy Amnezia activity")
|
||||||
|
unregisterBroadcastReceiver(notificationStateReceiver)
|
||||||
|
notificationStateReceiver = null
|
||||||
mainScope.cancel()
|
mainScope.cancel()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
when (requestCode) {
|
Log.d(TAG, "Process activity result, code: ${actionCodeToString(requestCode)}, " +
|
||||||
CREATE_FILE_ACTION_CODE -> {
|
"resultCode: $resultCode, data: $data")
|
||||||
when (resultCode) {
|
actionResultHandlers[requestCode]?.let { handler ->
|
||||||
RESULT_OK -> {
|
when (resultCode) {
|
||||||
data?.data?.let { uri ->
|
RESULT_OK -> handler.onSuccess(data)
|
||||||
alterDocument(uri)
|
else -> handler.onFail(data)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
handler.onAny(data)
|
||||||
|
actionResultHandlers.remove(requestCode)
|
||||||
|
} ?: super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
}
|
||||||
|
|
||||||
OPEN_FILE_ACTION_CODE -> {
|
private fun startActivityForResult(intent: Intent, requestCode: Int, handler: ActivityResultHandler) {
|
||||||
when (resultCode) {
|
actionResultHandlers[requestCode] = handler
|
||||||
RESULT_OK -> data?.data?.toString() ?: ""
|
startActivityForResult(intent, requestCode)
|
||||||
else -> ""
|
}
|
||||||
}.let { uri ->
|
|
||||||
QtAndroidController.onFileOpened(uri)
|
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()
|
||||||
}
|
}
|
||||||
|
handler.onAny()
|
||||||
|
permissionRequestHandlers.remove(requestCode)
|
||||||
|
} ?: super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
}
|
||||||
|
|
||||||
CHECK_VPN_PERMISSION_ACTION_CODE -> {
|
private fun requestPermission(permission: String, requestCode: Int, handler: PermissionRequestHandler) {
|
||||||
when (resultCode) {
|
permissionRequestHandlers[requestCode] = handler
|
||||||
RESULT_OK -> {
|
requestPermissions(arrayOf(permission), requestCode)
|
||||||
Log.d(TAG, "Vpn permission granted")
|
|
||||||
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
|
|
||||||
checkVpnPermissionCallbacks?.run { onSuccess() }
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
|
||||||
Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
|
|
||||||
checkVpnPermissionCallbacks?.run { onFail() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
checkVpnPermissionCallbacks = null
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> super.onActivityResult(requestCode, resultCode, data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -262,22 +292,75 @@ class AmneziaActivity : QtActivity() {
|
|||||||
/**
|
/**
|
||||||
* Methods of starting and stopping VpnService
|
* Methods of starting and stopping VpnService
|
||||||
*/
|
*/
|
||||||
private fun checkVpnPermissionAndStart(vpnConfig: String) {
|
@MainThread
|
||||||
checkVpnPermission(
|
private fun checkVpnPermission(onPermissionGranted: () -> Unit) {
|
||||||
onSuccess = { startVpn(vpnConfig) },
|
Log.d(TAG, "Check VPN permission")
|
||||||
onFail = QtAndroidController::onVpnPermissionRejected
|
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()
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
private fun showOnVpnPermissionRejectDialog() {
|
||||||
private fun checkVpnPermission(onSuccess: () -> Unit, onFail: () -> Unit) {
|
AlertDialog.Builder(this)
|
||||||
Log.d(TAG, "Check VPN permission")
|
.setTitle(R.string.vpnSetupFailed)
|
||||||
VpnService.prepare(applicationContext)?.let {
|
.setMessage(R.string.vpnSetupFailedMessage)
|
||||||
checkVpnPermissionCallbacks = CheckVpnPermissionCallbacks(onSuccess, onFail)
|
.setNegativeButton(R.string.ok) { _, _ -> }
|
||||||
startActivityForResult(it, CHECK_VPN_PERMISSION_ACTION_CODE)
|
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
|
||||||
return
|
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
|
||||||
|
}
|
||||||
|
.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()
|
||||||
}
|
}
|
||||||
onSuccess()
|
}
|
||||||
|
|
||||||
|
@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
|
@MainThread
|
||||||
@@ -305,28 +388,21 @@ class AmneziaActivity : QtActivity() {
|
|||||||
Intent(this, AmneziaVpnService::class.java).apply {
|
Intent(this, AmneziaVpnService::class.java).apply {
|
||||||
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
putExtra(MSG_VPN_CONFIG, vpnConfig)
|
||||||
}.also {
|
}.also {
|
||||||
ContextCompat.startForegroundService(this, it)
|
try {
|
||||||
|
ContextCompat.startForegroundService(this, it)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||||
|
QtAndroidController.onServiceError()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainThread
|
||||||
private fun disconnectFromVpn() {
|
private fun disconnectFromVpn() {
|
||||||
Log.d(TAG, "Disconnect from VPN")
|
Log.d(TAG, "Disconnect from VPN")
|
||||||
vpnServiceMessenger.send(Action.DISCONNECT)
|
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
|
* Methods called by Qt
|
||||||
*/
|
*/
|
||||||
@@ -340,7 +416,11 @@ class AmneziaActivity : QtActivity() {
|
|||||||
fun start(vpnConfig: String) {
|
fun start(vpnConfig: String) {
|
||||||
Log.v(TAG, "Start VPN")
|
Log.v(TAG, "Start VPN")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
checkVpnPermissionAndStart(vpnConfig)
|
checkVpnPermission {
|
||||||
|
checkNotificationPermission {
|
||||||
|
startVpn(vpnConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -372,14 +452,26 @@ class AmneziaActivity : QtActivity() {
|
|||||||
fun saveFile(fileName: String, data: String) {
|
fun saveFile(fileName: String, data: String) {
|
||||||
Log.d(TAG, "Save file $fileName")
|
Log.d(TAG, "Save file $fileName")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
tmpFileContentToSave = data
|
|
||||||
|
|
||||||
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
Intent(Intent.ACTION_CREATE_DOCUMENT).apply {
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
type = "text/*"
|
type = "text/*"
|
||||||
putExtra(Intent.EXTRA_TITLE, fileName)
|
putExtra(Intent.EXTRA_TITLE, fileName)
|
||||||
}.also {
|
}.also {
|
||||||
startActivityForResult(it, CREATE_FILE_ACTION_CODE)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -387,38 +479,47 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun openFile(filter: String?) {
|
fun openFile(filter: String?) {
|
||||||
Log.v(TAG, "Open file with filter: $filter")
|
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()
|
||||||
|
|
||||||
val mimeTypes = if (!filter.isNullOrEmpty()) {
|
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
||||||
val extensionRegex = "\\*\\.[a-z .]+".toRegex(IGNORE_CASE)
|
addCategory(Intent.CATEGORY_OPENABLE)
|
||||||
val mime = MimeTypeMap.getSingleton()
|
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
||||||
extensionRegex.findAll(filter).map {
|
if ("*/*" in mimeTypes) {
|
||||||
mime.getMimeTypeFromExtension(it.value.drop(2))
|
|
||||||
}.filterNotNull().toSet()
|
|
||||||
} else emptySet()
|
|
||||||
|
|
||||||
Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
|
|
||||||
addCategory(Intent.CATEGORY_OPENABLE)
|
|
||||||
Log.v(TAG, "File mimyType filter: $mimeTypes")
|
|
||||||
when (mimeTypes.size) {
|
|
||||||
1 -> type = mimeTypes.first()
|
|
||||||
|
|
||||||
in 2..Int.MAX_VALUE -> {
|
|
||||||
type = "*/*"
|
type = "*/*"
|
||||||
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
} else {
|
||||||
|
when (mimeTypes.size) {
|
||||||
|
1 -> type = mimeTypes.first()
|
||||||
|
|
||||||
|
in 2..Int.MAX_VALUE -> {
|
||||||
|
type = "*/*"
|
||||||
|
putExtra(EXTRA_MIME_TYPES, mimeTypes.toTypedArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> type = "*/*"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}.also {
|
||||||
else -> type = "*/*"
|
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")
|
@Suppress("unused")
|
||||||
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
fun isCameraPresent(): Boolean = applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
|
||||||
|
|
||||||
@@ -432,7 +533,7 @@ class AmneziaActivity : QtActivity() {
|
|||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun setSaveLogs(enabled: Boolean) {
|
fun setSaveLogs(enabled: Boolean) {
|
||||||
Log.d(TAG, "Set save logs: $enabled")
|
Log.v(TAG, "Set save logs: $enabled")
|
||||||
mainScope.launch {
|
mainScope.launch {
|
||||||
Log.saveLogs = enabled
|
Log.saveLogs = enabled
|
||||||
vpnServiceMessenger.send {
|
vpnServiceMessenger.send {
|
||||||
@@ -452,7 +553,9 @@ class AmneziaActivity : QtActivity() {
|
|||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
Log.v(TAG, "Clear logs")
|
Log.v(TAG, "Clear logs")
|
||||||
Log.clearLogs()
|
mainScope.launch {
|
||||||
|
Log.clearLogs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
@@ -463,4 +566,104 @@ class AmneziaActivity : QtActivity() {
|
|||||||
window.setFlags(flag, LayoutParams.FLAG_SECURE)
|
window.setFlags(flag, LayoutParams.FLAG_SECURE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun minimizeApp() {
|
||||||
|
Log.v(TAG, "Minimize application")
|
||||||
|
mainScope.launch {
|
||||||
|
moveTaskToBack(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getAppList(): String {
|
||||||
|
Log.v(TAG, "Get app list")
|
||||||
|
var appList = ""
|
||||||
|
runBlocking {
|
||||||
|
mainScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
appList = AppListProvider.getAppList(packageManager, packageName)
|
||||||
|
}
|
||||||
|
}.join()
|
||||||
|
}
|
||||||
|
return appList
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
fun getAppIcon(packageName: String, width: Int, height: Int): Bitmap {
|
||||||
|
Log.v(TAG, "Get app icon")
|
||||||
|
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,14 +3,11 @@ package org.amnezia.vpn
|
|||||||
import androidx.camera.camera2.Camera2Config
|
import androidx.camera.camera2.Camera2Config
|
||||||
import androidx.camera.core.CameraSelector
|
import androidx.camera.core.CameraSelector
|
||||||
import androidx.camera.core.CameraXConfig
|
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.Log
|
||||||
import org.amnezia.vpn.util.Prefs
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.qtproject.qt.android.bindings.QtApplication
|
import org.qtproject.qt.android.bindings.QtApplication
|
||||||
|
|
||||||
private const val TAG = "AmneziaApplication"
|
private const val TAG = "AmneziaApplication"
|
||||||
const val NOTIFICATION_CHANNEL_ID: String = "org.amnezia.vpn.notification"
|
|
||||||
|
|
||||||
class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
||||||
|
|
||||||
@@ -20,7 +17,7 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
|||||||
Log.init(this)
|
Log.init(this)
|
||||||
VpnStateStore.init(this)
|
VpnStateStore.init(this)
|
||||||
Log.d(TAG, "Create Amnezia application")
|
Log.d(TAG, "Create Amnezia application")
|
||||||
createNotificationChannel()
|
ServiceNotification.createNotificationChannel(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getCameraXConfig(): CameraXConfig = CameraXConfig.Builder
|
override fun getCameraXConfig(): CameraXConfig = CameraXConfig.Builder
|
||||||
@@ -28,14 +25,4 @@ class AmneziaApplication : QtApplication(), CameraXConfig.Provider {
|
|||||||
.setMinimumLoggingLevel(android.util.Log.ERROR)
|
.setMinimumLoggingLevel(android.util.Log.ERROR)
|
||||||
.setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
|
.setAvailableCamerasLimiter(CameraSelector.DEFAULT_BACK_CAMERA)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun createNotificationChannel() {
|
|
||||||
NotificationManagerCompat.from(this).createNotificationChannel(
|
|
||||||
Builder(NOTIFICATION_CHANNEL_ID, NotificationManagerCompat.IMPORTANCE_LOW)
|
|
||||||
.setName("AmneziaVPN")
|
|
||||||
.setDescription("AmneziaVPN service notification")
|
|
||||||
.setShowBadge(false)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
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,11 +188,16 @@ class AmneziaTileService : TileService() {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startVpnService() =
|
private fun startVpnService() {
|
||||||
ContextCompat.startForegroundService(
|
try {
|
||||||
applicationContext,
|
ContextCompat.startForegroundService(
|
||||||
Intent(this, AmneziaVpnService::class.java)
|
applicationContext,
|
||||||
)
|
Intent(this, AmneziaVpnService::class.java)
|
||||||
|
)
|
||||||
|
} catch (e: SecurityException) {
|
||||||
|
Log.e(TAG, "Failed to start AmneziaVpnService: $e")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
private fun connectToVpn() = vpnServiceMessenger.send(Action.CONNECT)
|
||||||
|
|
||||||
@@ -230,7 +235,7 @@ class AmneziaTileService : TileService() {
|
|||||||
val tile = qsTile ?: return
|
val tile = qsTile ?: return
|
||||||
tile.apply {
|
tile.apply {
|
||||||
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
label = vpnState.serverName ?: DEFAULT_TILE_LABEL
|
||||||
when (vpnState.protocolState) {
|
when (val protocolState = vpnState.protocolState) {
|
||||||
CONNECTED -> {
|
CONNECTED -> {
|
||||||
state = Tile.STATE_ACTIVE
|
state = Tile.STATE_ACTIVE
|
||||||
subtitleCompat = null
|
subtitleCompat = null
|
||||||
@@ -241,14 +246,9 @@ class AmneziaTileService : TileService() {
|
|||||||
subtitleCompat = null
|
subtitleCompat = null
|
||||||
}
|
}
|
||||||
|
|
||||||
CONNECTING, RECONNECTING -> {
|
CONNECTING, DISCONNECTING, RECONNECTING -> {
|
||||||
state = Tile.STATE_UNAVAILABLE
|
state = Tile.STATE_UNAVAILABLE
|
||||||
subtitleCompat = resources.getString(R.string.connecting)
|
subtitleCompat = getString(protocolState)
|
||||||
}
|
|
||||||
|
|
||||||
DISCONNECTING -> {
|
|
||||||
state = Tile.STATE_UNAVAILABLE
|
|
||||||
subtitleCompat = resources.getString(R.string.disconnecting)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateTile()
|
updateTile()
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ package org.amnezia.vpn
|
|||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE
|
||||||
import android.app.Notification
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MANIFEST
|
||||||
@@ -15,10 +15,12 @@ import android.os.IBinder
|
|||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.os.Message
|
import android.os.Message
|
||||||
import android.os.Messenger
|
import android.os.Messenger
|
||||||
|
import android.os.PowerManager
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import androidx.annotation.MainThread
|
import androidx.annotation.MainThread
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.ServiceCompat
|
import androidx.core.app.ServiceCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
@@ -54,11 +56,14 @@ import org.amnezia.vpn.protocol.wireguard.Wireguard
|
|||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
import org.amnezia.vpn.util.Prefs
|
import org.amnezia.vpn.util.Prefs
|
||||||
import org.amnezia.vpn.util.net.NetworkState
|
import org.amnezia.vpn.util.net.NetworkState
|
||||||
|
import org.amnezia.vpn.util.net.TrafficStats
|
||||||
import org.json.JSONException
|
import org.json.JSONException
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
private const val TAG = "AmneziaVpnService"
|
private const val TAG = "AmneziaVpnService"
|
||||||
|
|
||||||
|
const val ACTION_DISCONNECT = "org.amnezia.vpn.action.disconnect"
|
||||||
|
|
||||||
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
const val MSG_VPN_CONFIG = "VPN_CONFIG"
|
||||||
const val MSG_ERROR = "ERROR"
|
const val MSG_ERROR = "ERROR"
|
||||||
const val MSG_SAVE_LOGS = "SAVE_LOGS"
|
const val MSG_SAVE_LOGS = "SAVE_LOGS"
|
||||||
@@ -69,8 +74,8 @@ private const val PREFS_CONFIG_KEY = "LAST_CONF"
|
|||||||
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
private const val PREFS_SERVER_NAME = "LAST_SERVER_NAME"
|
||||||
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
private const val PREFS_SERVER_INDEX = "LAST_SERVER_INDEX"
|
||||||
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
private const val PROCESS_NAME = "org.amnezia.vpn:amneziaVpnService"
|
||||||
private const val NOTIFICATION_ID = 1337
|
// private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
||||||
private const val STATISTICS_SENDING_TIMEOUT = 1000L
|
private const val TRAFFIC_STATS_UPDATE_TIMEOUT = 1000L
|
||||||
private const val DISCONNECT_TIMEOUT = 5000L
|
private const val DISCONNECT_TIMEOUT = 5000L
|
||||||
private const val STOP_SERVICE_TIMEOUT = 5000L
|
private const val STOP_SERVICE_TIMEOUT = 5000L
|
||||||
|
|
||||||
@@ -96,8 +101,14 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
private var connectionJob: Job? = null
|
private var connectionJob: Job? = null
|
||||||
private var disconnectionJob: Job? = null
|
private var disconnectionJob: Job? = null
|
||||||
private var statisticsSendingJob: Job? = null
|
private var trafficStatsUpdateJob: Job? = null
|
||||||
|
// private var statisticsSendingJob: Job? = null
|
||||||
private lateinit var networkState: NetworkState
|
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 clientMessengers = ConcurrentHashMap<Messenger, IpcMessenger>()
|
||||||
|
|
||||||
private val isActivityConnected
|
private val isActivityConnected
|
||||||
@@ -131,13 +142,13 @@ class AmneziaVpnService : VpnService() {
|
|||||||
val messenger = IpcMessenger(msg.replyTo, clientName)
|
val messenger = IpcMessenger(msg.replyTo, clientName)
|
||||||
clientMessengers[msg.replyTo] = messenger
|
clientMessengers[msg.replyTo] = messenger
|
||||||
Log.d(TAG, "Messenger client '$clientName' was registered")
|
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 -> {
|
Action.UNREGISTER_CLIENT -> {
|
||||||
clientMessengers.remove(msg.replyTo)?.let {
|
clientMessengers.remove(msg.replyTo)?.let {
|
||||||
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
|
Log.d(TAG, "Messenger client '${it.name}' was unregistered")
|
||||||
if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
|
// if (it.name == ACTIVITY_MESSENGER_NAME) stopSendingStatistics()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,6 +170,10 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action.NOTIFICATION_PERMISSION_GRANTED -> {
|
||||||
|
enableNotification()
|
||||||
|
}
|
||||||
|
|
||||||
Action.SET_SAVE_LOGS -> {
|
Action.SET_SAVE_LOGS -> {
|
||||||
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
|
Log.saveLogs = msg.data.getBoolean(MSG_SAVE_LOGS)
|
||||||
}
|
}
|
||||||
@@ -181,25 +196,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
else -> 0
|
else -> 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private val notification: Notification by lazy(NONE) {
|
private val serviceNotification: ServiceNotification by lazy(NONE) { ServiceNotification(this) }
|
||||||
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
|
* Service overloaded methods
|
||||||
@@ -212,14 +209,14 @@ class AmneziaVpnService : VpnService() {
|
|||||||
loadServerData()
|
loadServerData()
|
||||||
launchProtocolStateHandler()
|
launchProtocolStateHandler()
|
||||||
networkState = NetworkState(this, ::reconnect)
|
networkState = NetworkState(this, ::reconnect)
|
||||||
|
trafficStats = TrafficStats()
|
||||||
|
registerBroadcastReceivers()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
val isAlwaysOnCompat =
|
val isAlwaysOn = intent != null && intent.action == SERVICE_INTERFACE
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) isAlwaysOn
|
|
||||||
else intent?.component?.packageName != packageName
|
|
||||||
|
|
||||||
if (isAlwaysOnCompat) {
|
if (isAlwaysOn) {
|
||||||
Log.d(TAG, "Start service via Always-on")
|
Log.d(TAG, "Start service via Always-on")
|
||||||
connect()
|
connect()
|
||||||
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
|
} else if (intent?.getBooleanExtra(AFTER_PERMISSION_CHECK, false) == true) {
|
||||||
@@ -229,7 +226,10 @@ class AmneziaVpnService : VpnService() {
|
|||||||
Log.d(TAG, "Start service")
|
Log.d(TAG, "Start service")
|
||||||
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
connect(intent?.getStringExtra(MSG_VPN_CONFIG))
|
||||||
}
|
}
|
||||||
ServiceCompat.startForeground(this, NOTIFICATION_ID, notification, foregroundServiceTypeCompat)
|
ServiceCompat.startForeground(
|
||||||
|
this, NOTIFICATION_ID, serviceNotification.buildNotification(serverName, protocolState.value),
|
||||||
|
foregroundServiceTypeCompat
|
||||||
|
)
|
||||||
return START_REDELIVER_INTENT
|
return START_REDELIVER_INTENT
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,6 +269,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Log.d(TAG, "Destroy service")
|
Log.d(TAG, "Destroy service")
|
||||||
|
unregisterBroadcastReceivers()
|
||||||
runBlocking {
|
runBlocking {
|
||||||
disconnect()
|
disconnect()
|
||||||
disconnectionJob?.join()
|
disconnectionJob?.join()
|
||||||
@@ -289,6 +290,63 @@ class AmneziaVpnService : VpnService() {
|
|||||||
stopSelf()
|
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
|
* Methods responsible for processing VPN connection
|
||||||
*/
|
*/
|
||||||
@@ -297,29 +355,8 @@ class AmneziaVpnService : VpnService() {
|
|||||||
// drop first default UNKNOWN state
|
// drop first default UNKNOWN state
|
||||||
protocolState.drop(1).collect { protocolState ->
|
protocolState.drop(1).collect { protocolState ->
|
||||||
Log.d(TAG, "Protocol state changed: $protocolState")
|
Log.d(TAG, "Protocol state changed: $protocolState")
|
||||||
when (protocolState) {
|
|
||||||
CONNECTED -> {
|
|
||||||
networkState.bindNetworkListener()
|
|
||||||
if (isActivityConnected) launchSendingStatistics()
|
|
||||||
}
|
|
||||||
|
|
||||||
DISCONNECTED -> {
|
serviceNotification.updateNotification(serverName, protocolState)
|
||||||
networkState.unbindNetworkListener()
|
|
||||||
stopSendingStatistics()
|
|
||||||
if (!isServiceBound) stopService()
|
|
||||||
}
|
|
||||||
|
|
||||||
DISCONNECTING -> {
|
|
||||||
networkState.unbindNetworkListener()
|
|
||||||
stopSendingStatistics()
|
|
||||||
}
|
|
||||||
|
|
||||||
RECONNECTING -> {
|
|
||||||
stopSendingStatistics()
|
|
||||||
}
|
|
||||||
|
|
||||||
CONNECTING, UNKNOWN -> {}
|
|
||||||
}
|
|
||||||
|
|
||||||
clientMessengers.send {
|
clientMessengers.send {
|
||||||
ServiceEvent.STATUS_CHANGED.packToMessage {
|
ServiceEvent.STATUS_CHANGED.packToMessage {
|
||||||
@@ -328,13 +365,41 @@ class AmneziaVpnService : VpnService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
VpnStateStore.store { VpnState(protocolState, serverName, serverIndex) }
|
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() {
|
private fun launchSendingStatistics() {
|
||||||
/* if (isServiceBound && isConnected) {
|
if (isServiceBound && isConnected) {
|
||||||
statisticsSendingJob = mainScope.launch {
|
statisticsSendingJob = mainScope.launch {
|
||||||
while (true) {
|
while (true) {
|
||||||
clientMessenger.send {
|
clientMessenger.send {
|
||||||
@@ -345,12 +410,62 @@ class AmneziaVpnService : VpnService() {
|
|||||||
delay(STATISTICS_SENDING_TIMEOUT)
|
delay(STATISTICS_SENDING_TIMEOUT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} */
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
private fun stopSendingStatistics() {
|
private fun stopSendingStatistics() {
|
||||||
statisticsSendingJob?.cancel()
|
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
|
@MainThread
|
||||||
@@ -473,6 +588,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
private fun saveServerData(config: JSONObject?) {
|
private fun saveServerData(config: JSONObject?) {
|
||||||
serverName = config?.opt("description") as String?
|
serverName = config?.opt("description") as String?
|
||||||
serverIndex = config?.opt("serverIndex") as Int? ?: -1
|
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_NAME, serverName)
|
||||||
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
|
Prefs.save(PREFS_SERVER_INDEX, serverIndex)
|
||||||
}
|
}
|
||||||
@@ -480,6 +596,7 @@ class AmneziaVpnService : VpnService() {
|
|||||||
private fun loadServerData() {
|
private fun loadServerData() {
|
||||||
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
|
serverName = Prefs.load<String>(PREFS_SERVER_NAME).ifBlank { null }
|
||||||
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
|
if (serverName != null) serverIndex = Prefs.load(PREFS_SERVER_INDEX)
|
||||||
|
Log.d(TAG, "Load server data: ($serverIndex, $serverName)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkPermission(): Boolean =
|
private fun checkPermission(): Boolean =
|
||||||
@@ -496,9 +613,8 @@ class AmneziaVpnService : VpnService() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun isRunning(context: Context): Boolean =
|
fun isRunning(context: Context): Boolean =
|
||||||
(context.getSystemService(ACTIVITY_SERVICE) as ActivityManager)
|
context.getSystemService<ActivityManager>()!!.runningAppProcesses.any {
|
||||||
.runningAppProcesses.any {
|
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
||||||
it.processName == PROCESS_NAME && it.importance <= IMPORTANCE_FOREGROUND_SERVICE
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import android.Manifest.permission.INTERNET
|
||||||
|
import android.content.pm.ApplicationInfo
|
||||||
|
import android.content.pm.PackageInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.pm.PackageManager.NameNotFoundException
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Bitmap.Config.ARGB_8888
|
||||||
|
import androidx.core.graphics.drawable.toBitmapOrNull
|
||||||
|
import org.amnezia.vpn.util.Log
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
private const val TAG = "AppListProvider"
|
||||||
|
|
||||||
|
object AppListProvider {
|
||||||
|
fun getAppList(pm: PackageManager, selfPackageName: String): String {
|
||||||
|
val jsonArray = JSONArray()
|
||||||
|
pm.getPackagesHoldingPermissions(arrayOf(INTERNET), 0)
|
||||||
|
.filter { it.packageName != selfPackageName }
|
||||||
|
.map { App(it, pm) }
|
||||||
|
.sortedWith(App::compareTo)
|
||||||
|
.map(App::toJson)
|
||||||
|
.forEach(jsonArray::put)
|
||||||
|
return jsonArray.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAppIcon(pm: PackageManager, packageName: String, width: Int, height: Int): Bitmap {
|
||||||
|
val icon = try {
|
||||||
|
pm.getApplicationIcon(packageName)
|
||||||
|
} catch (e: NameNotFoundException) {
|
||||||
|
Log.e(TAG, "Package $packageName was not found: $e")
|
||||||
|
pm.defaultActivityIcon
|
||||||
|
}
|
||||||
|
val w: Int = if (width > 0) width else icon.intrinsicWidth
|
||||||
|
val h: Int = if (height > 0) height else icon.intrinsicHeight
|
||||||
|
return icon.toBitmapOrNull(w, h, ARGB_8888)
|
||||||
|
?: Bitmap.createBitmap(w, h, ARGB_8888)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class App(pi: PackageInfo, pm: PackageManager, ai: ApplicationInfo = pi.applicationInfo) : Comparable<App> {
|
||||||
|
val name: String?
|
||||||
|
val packageName: String = pi.packageName
|
||||||
|
val icon: Boolean = ai.icon != 0
|
||||||
|
val isLaunchable: Boolean = pm.getLaunchIntentForPackage(packageName) != null
|
||||||
|
|
||||||
|
init {
|
||||||
|
val name = ai.loadLabel(pm).toString()
|
||||||
|
this.name = if (name != packageName) name else null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: App): Int {
|
||||||
|
val r = other.isLaunchable.compareTo(isLaunchable)
|
||||||
|
if (r != 0) return r
|
||||||
|
if (name != other.name) {
|
||||||
|
return when {
|
||||||
|
name == null -> 1
|
||||||
|
other.name == null -> -1
|
||||||
|
else -> String.CASE_INSENSITIVE_ORDER.compare(name, other.name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return String.CASE_INSENSITIVE_ORDER.compare(packageName, other.packageName)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(): JSONObject {
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
jsonObject.put("package", packageName)
|
||||||
|
jsonObject.put("name", name)
|
||||||
|
jsonObject.put("icon", icon)
|
||||||
|
jsonObject.put("launchable", isLaunchable)
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,7 @@ enum class Action : IpcMessage {
|
|||||||
CONNECT,
|
CONNECT,
|
||||||
DISCONNECT,
|
DISCONNECT,
|
||||||
REQUEST_STATUS,
|
REQUEST_STATUS,
|
||||||
|
NOTIFICATION_PERMISSION_GRANTED,
|
||||||
SET_SAVE_LOGS
|
SET_SAVE_LOGS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,189 +0,0 @@
|
|||||||
/* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
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
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
package org.amnezia.vpn
|
package org.amnezia.vpn
|
||||||
|
|
||||||
|
import android.app.AlertDialog
|
||||||
import android.app.KeyguardManager
|
import android.app.KeyguardManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
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
|
import android.net.VpnService
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.provider.Settings
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.result.ActivityResult
|
import androidx.activity.result.ActivityResult
|
||||||
@@ -29,11 +31,9 @@ class VpnRequestActivity : ComponentActivity() {
|
|||||||
val requestIntent = VpnService.prepare(applicationContext)
|
val requestIntent = VpnService.prepare(applicationContext)
|
||||||
if (requestIntent != null) {
|
if (requestIntent != null) {
|
||||||
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
if (getSystemService<KeyguardManager>()!!.isKeyguardLocked) {
|
||||||
userPresentReceiver = object : BroadcastReceiver() {
|
userPresentReceiver = registerBroadcastReceiver(Intent.ACTION_USER_PRESENT) {
|
||||||
override fun onReceive(context: Context?, intent: Intent?) =
|
requestLauncher.launch(requestIntent)
|
||||||
requestLauncher.launch(requestIntent)
|
|
||||||
}
|
}
|
||||||
registerReceiver(userPresentReceiver, IntentFilter(Intent.ACTION_USER_PRESENT))
|
|
||||||
} else {
|
} else {
|
||||||
requestLauncher.launch(requestIntent)
|
requestLauncher.launch(requestIntent)
|
||||||
}
|
}
|
||||||
@@ -45,26 +45,49 @@ class VpnRequestActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
userPresentReceiver?.let {
|
unregisterBroadcastReceiver(userPresentReceiver)
|
||||||
unregisterReceiver(it)
|
userPresentReceiver = null
|
||||||
}
|
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun checkRequestResult(result: ActivityResult) {
|
private fun checkRequestResult(result: ActivityResult) {
|
||||||
when (result.resultCode) {
|
when (val resultCode = result.resultCode) {
|
||||||
RESULT_OK -> onPermissionGranted()
|
RESULT_OK -> {
|
||||||
else -> Toast.makeText(this, "Vpn permission denied", Toast.LENGTH_LONG).show()
|
onPermissionGranted()
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
Log.w(TAG, "Vpn permission denied, resultCode: $resultCode")
|
||||||
|
showOnVpnPermissionRejectDialog()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onPermissionGranted() {
|
private fun onPermissionGranted() {
|
||||||
Toast.makeText(this, "Vpn permission granted", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, resources.getString(R.string.vpnGranted), Toast.LENGTH_LONG).show()
|
||||||
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
Intent(applicationContext, AmneziaVpnService::class.java).apply {
|
||||||
putExtra(AFTER_PERMISSION_CHECK, true)
|
putExtra(AFTER_PERMISSION_CHECK, true)
|
||||||
}.also {
|
}.also {
|
||||||
ContextCompat.startForegroundService(this, it)
|
ContextCompat.startForegroundService(this, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun showOnVpnPermissionRejectDialog() {
|
||||||
|
AlertDialog.Builder(this, getDialogTheme())
|
||||||
|
.setTitle(R.string.vpnSetupFailed)
|
||||||
|
.setMessage(R.string.vpnSetupFailedMessage)
|
||||||
|
.setNegativeButton(R.string.ok) { _, _ -> }
|
||||||
|
.setPositiveButton(R.string.openVpnSettings) { _, _ ->
|
||||||
|
startActivity(Intent(Settings.ACTION_VPN_SETTINGS))
|
||||||
|
}
|
||||||
|
.setOnDismissListener { finish() }
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDialogTheme(): Int =
|
||||||
|
if (resources.configuration.uiMode and UI_MODE_NIGHT_MASK == UI_MODE_NIGHT_YES)
|
||||||
|
android.R.style.Theme_DeviceDefault_Dialog_Alert
|
||||||
|
else
|
||||||
|
android.R.style.Theme_DeviceDefault_Light_Dialog_Alert
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.app.Application
|
|||||||
import androidx.datastore.core.MultiProcessDataStoreFactory
|
import androidx.datastore.core.MultiProcessDataStoreFactory
|
||||||
import androidx.datastore.core.Serializer
|
import androidx.datastore.core.Serializer
|
||||||
import androidx.datastore.dataStoreFile
|
import androidx.datastore.dataStoreFile
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.io.ObjectInputStream
|
import java.io.ObjectInputStream
|
||||||
import java.io.ObjectOutputStream
|
import java.io.ObjectOutputStream
|
||||||
@@ -59,7 +61,8 @@ private class VpnStateSerializer : Serializer<VpnState> {
|
|||||||
|
|
||||||
override suspend fun readFrom(input: InputStream): VpnState {
|
override suspend fun readFrom(input: InputStream): VpnState {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
ObjectInputStream(input).use {
|
val bios = ByteArrayInputStream(input.readBytes())
|
||||||
|
ObjectInputStream(bios).use {
|
||||||
it.readObject() as VpnState
|
it.readObject() as VpnState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,9 +70,11 @@ private class VpnStateSerializer : Serializer<VpnState> {
|
|||||||
|
|
||||||
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
override suspend fun writeTo(t: VpnState, output: OutputStream) {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
ObjectOutputStream(output).use {
|
val baos = ByteArrayOutputStream()
|
||||||
|
ObjectOutputStream(baos).use {
|
||||||
it.writeObject(t)
|
it.writeObject(t)
|
||||||
}
|
}
|
||||||
|
output.write(baos.toByteArray())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ object QtAndroidController {
|
|||||||
external fun onServiceError()
|
external fun onServiceError()
|
||||||
|
|
||||||
external fun onVpnPermissionRejected()
|
external fun onVpnPermissionRejected()
|
||||||
|
external fun onNotificationStateChanged()
|
||||||
external fun onVpnStateChanged(stateCode: Int)
|
external fun onVpnStateChanged(stateCode: Int)
|
||||||
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
external fun onStatisticsUpdate(rxBytes: Long, txBytes: Long)
|
||||||
|
|
||||||
|
|||||||
@@ -17,5 +17,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.androidx.core)
|
||||||
|
implementation(libs.kotlinx.coroutines)
|
||||||
implementation(libs.androidx.security.crypto)
|
implementation(libs.androidx.security.crypto)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,9 +109,11 @@ object Log {
|
|||||||
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
|
"${deviceInfo()}\n${readLogs()}\nLOGCAT:\n${getLogcat()}"
|
||||||
|
|
||||||
fun clearLogs() {
|
fun clearLogs() {
|
||||||
withLock {
|
if (logDir.exists()) {
|
||||||
logFile.delete()
|
withLock {
|
||||||
rotateLogFile.delete()
|
logFile.delete()
|
||||||
|
rotateLogFile.delete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,21 @@
|
|||||||
package org.amnezia.vpn.util.net
|
package org.amnezia.vpn.util.net
|
||||||
|
|
||||||
|
import java.net.Inet4Address
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
data class InetEndpoint(val address: InetAddress, val port: Int) {
|
data class InetEndpoint(val address: InetAddress, val port: Int) {
|
||||||
|
|
||||||
override fun toString(): String = "${address.hostAddress}:$port"
|
override fun toString(): String = if (address is Inet4Address) {
|
||||||
|
"${address.ip}:$port"
|
||||||
|
} else {
|
||||||
|
"[${address.ip}]:$port"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(data: String): InetEndpoint {
|
fun parse(data: String): InetEndpoint {
|
||||||
val split = data.split(":")
|
val i = data.lastIndexOf(':')
|
||||||
val address = parseInetAddress(split.first())
|
val address = parseInetAddress(data.substring(0, i))
|
||||||
val port = split.last().toInt()
|
val port = data.substring(i + 1).toInt()
|
||||||
return InetEndpoint(address, port)
|
return InetEndpoint(address, port)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,11 @@ data class InetNetwork(val address: InetAddress, val mask: Int) {
|
|||||||
|
|
||||||
constructor(address: InetAddress) : this(address, address.maxPrefixLength)
|
constructor(address: InetAddress) : this(address, address.maxPrefixLength)
|
||||||
|
|
||||||
override fun toString(): String = "${address.hostAddress}/$mask"
|
val isIpv4: Boolean = address is Inet4Address
|
||||||
|
val isIpv6: Boolean
|
||||||
|
get() = !isIpv4
|
||||||
|
|
||||||
|
override fun toString(): String = "${address.ip}/$mask"
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun parse(data: String): InetNetwork {
|
fun parse(data: String): InetNetwork {
|
||||||
|
|||||||
@@ -3,12 +3,17 @@ package org.amnezia.vpn.util.net
|
|||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
class IpAddress private constructor(private val address: UByteArray) : Comparable<IpAddress> {
|
internal class IpAddress private constructor(private val address: UByteArray) : Comparable<IpAddress> {
|
||||||
|
|
||||||
val size: Int = address.size
|
val size: Int = address.size
|
||||||
val lastIndex: Int = address.lastIndex
|
val lastIndex: Int = address.lastIndex
|
||||||
val maxMask: Int = size * 8
|
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(inetAddress: InetAddress) : this(inetAddress.address.asUByteArray())
|
||||||
|
|
||||||
constructor(ipAddress: String) : this(parseInetAddress(ipAddress))
|
constructor(ipAddress: String) : this(parseInetAddress(ipAddress))
|
||||||
@@ -43,6 +48,8 @@ class IpAddress private constructor(private val address: UByteArray) : Comparabl
|
|||||||
return copy
|
return copy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isMinIp(): Boolean = address.all { it == 0x00u.toUByte() }
|
||||||
|
|
||||||
fun isMaxIp(): Boolean = address.all { it == 0xffu.toUByte() }
|
fun isMaxIp(): Boolean = address.all { it == 0xffu.toUByte() }
|
||||||
|
|
||||||
override fun compareTo(other: IpAddress): Int {
|
override fun compareTo(other: IpAddress): Int {
|
||||||
@@ -74,12 +81,14 @@ class IpAddress private constructor(private val address: UByteArray) : Comparabl
|
|||||||
private fun toIpv6String(): String {
|
private fun toIpv6String(): String {
|
||||||
val sb = StringBuilder()
|
val sb = StringBuilder()
|
||||||
var i = 0
|
var i = 0
|
||||||
|
var block: Int
|
||||||
while (i < size) {
|
while (i < size) {
|
||||||
sb.append(address[i++].toHexString())
|
block = address[i++].toInt() shl 8
|
||||||
sb.append(address[i++].toHexString())
|
block += address[i++].toInt()
|
||||||
|
sb.append(block.toHexString(hexFormat))
|
||||||
sb.append(':')
|
sb.append(':')
|
||||||
}
|
}
|
||||||
sb.deleteAt(sb.lastIndex)
|
sb.deleteAt(sb.lastIndex)
|
||||||
return sb.toString()
|
return convertIpv6ToCanonicalForm(sb.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,24 @@ package org.amnezia.vpn.util.net
|
|||||||
|
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
class IpRange(private val start: IpAddress, private val end: IpAddress) : Comparable<IpRange> {
|
class IpRange internal constructor(
|
||||||
|
internal val start: IpAddress,
|
||||||
|
internal val end: IpAddress
|
||||||
|
) : Comparable<IpRange> {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (start > end) throw IllegalArgumentException("Start IP: $start is greater then end IP: $end")
|
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]")
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(addresses: Pair<IpAddress, IpAddress>) : this(addresses.first, addresses.second)
|
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(inetAddress: InetAddress, mask: Int) : this(from(inetAddress, mask))
|
||||||
|
|
||||||
constructor(address: String, mask: Int) : this(parseInetAddress(address), mask)
|
constructor(address: String, mask: Int) : this(parseInetAddress(address), mask)
|
||||||
@@ -22,6 +32,13 @@ class IpRange(private val start: IpAddress, private val end: IpAddress) : Compar
|
|||||||
private fun isIntersect(other: IpRange): Boolean =
|
private fun isIntersect(other: IpRange): Boolean =
|
||||||
(start <= other.end) && (end >= other.start)
|
(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>? {
|
operator fun minus(other: IpRange): List<IpRange>? {
|
||||||
if (this in other) return emptyList()
|
if (this in other) return emptyList()
|
||||||
if (!isIntersect(other)) return null
|
if (!isIntersect(other)) return null
|
||||||
@@ -94,9 +111,7 @@ class IpRange(private val start: IpAddress, private val end: IpAddress) : Compar
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String = if (start == end) "<$start>" else "<$start - $end>"
|
||||||
return "$start - $end"
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private fun from(inetAddress: InetAddress, mask: Int): Pair<IpAddress, IpAddress> {
|
private fun from(inetAddress: InetAddress, mask: Int): Pair<IpAddress, IpAddress> {
|
||||||
|
|||||||
@@ -1,15 +1,35 @@
|
|||||||
package org.amnezia.vpn.util.net
|
package org.amnezia.vpn.util.net
|
||||||
|
|
||||||
class IpRangeSet(ipRange: IpRange = IpRange("0.0.0.0", 0)) {
|
class IpRangeSet {
|
||||||
|
|
||||||
private val ranges = sortedSetOf(ipRange)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun remove(ipRange: IpRange) {
|
fun remove(ipRange: IpRange) {
|
||||||
val iterator = ranges.iterator()
|
val iterator = ranges.iterator()
|
||||||
val splitRanges = mutableListOf<IpRange>()
|
val splitRanges = mutableListOf<IpRange>()
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
val range = iterator.next()
|
val curRange = iterator.next()
|
||||||
(range - ipRange)?.let { resultRanges ->
|
if (ipRange.end < curRange.start) break
|
||||||
|
(curRange - ipRange)?.let { resultRanges ->
|
||||||
iterator.remove()
|
iterator.remove()
|
||||||
splitRanges += resultRanges
|
splitRanges += resultRanges
|
||||||
}
|
}
|
||||||
@@ -17,10 +37,7 @@ class IpRangeSet(ipRange: IpRange = IpRange("0.0.0.0", 0)) {
|
|||||||
ranges += splitRanges
|
ranges += splitRanges
|
||||||
}
|
}
|
||||||
|
|
||||||
fun subnets(): List<InetNetwork> =
|
fun subnets(): List<InetNetwork> = ranges.map(IpRange::subnets).flatten()
|
||||||
ranges.map(IpRange::subnets).flatten()
|
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String = ranges.toString()
|
||||||
return ranges.toString()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,9 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
|
|||||||
import android.net.NetworkRequest
|
import android.net.NetworkRequest
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import kotlin.LazyThreadSafetyMode.NONE
|
import kotlin.LazyThreadSafetyMode.NONE
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import org.amnezia.vpn.util.Log
|
import org.amnezia.vpn.util.Log
|
||||||
|
|
||||||
private const val TAG = "NetworkState"
|
private const val TAG = "NetworkState"
|
||||||
@@ -28,7 +30,7 @@ class NetworkState(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val connectivityManager: ConnectivityManager by lazy(NONE) {
|
private val connectivityManager: ConnectivityManager by lazy(NONE) {
|
||||||
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
context.getSystemService<ConnectivityManager>()!!
|
||||||
}
|
}
|
||||||
|
|
||||||
private val networkRequest: NetworkRequest by lazy(NONE) {
|
private val networkRequest: NetworkRequest by lazy(NONE) {
|
||||||
@@ -80,13 +82,24 @@ class NetworkState(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun bindNetworkListener() {
|
suspend fun bindNetworkListener() {
|
||||||
if (isListenerBound) return
|
if (isListenerBound) return
|
||||||
Log.d(TAG, "Bind network listener")
|
Log.d(TAG, "Bind network listener")
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
connectivityManager.registerBestMatchingNetworkCallback(networkRequest, networkCallback, handler)
|
||||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback, handler)
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
connectivityManager.requestNetwork(networkRequest, networkCallback)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,12 +5,14 @@ import android.net.ConnectivityManager
|
|||||||
import android.net.InetAddresses
|
import android.net.InetAddresses
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
|
import java.lang.reflect.InvocationTargetException
|
||||||
import java.net.Inet4Address
|
import java.net.Inet4Address
|
||||||
import java.net.Inet6Address
|
import java.net.Inet6Address
|
||||||
import java.net.InetAddress
|
import java.net.InetAddress
|
||||||
|
|
||||||
fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
|
fun getLocalNetworks(context: Context, ipv6: Boolean): List<InetNetwork> {
|
||||||
val connectivityManager = context.getSystemService(ConnectivityManager::class.java)
|
val connectivityManager = context.getSystemService<ConnectivityManager>()!!
|
||||||
connectivityManager.activeNetwork?.let { network ->
|
connectivityManager.activeNetwork?.let { network ->
|
||||||
val netCapabilities = connectivityManager.getNetworkCapabilities(network)
|
val netCapabilities = connectivityManager.getNetworkCapabilities(network)
|
||||||
val linkProperties = connectivityManager.getLinkProperties(network)
|
val linkProperties = connectivityManager.getLinkProperties(network)
|
||||||
@@ -39,8 +41,28 @@ private val parseNumericAddressCompat: (String) -> InetAddress =
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
InetAddresses::parseNumericAddress
|
InetAddresses::parseNumericAddress
|
||||||
} else {
|
} else {
|
||||||
val m = InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
|
try {
|
||||||
fun(address: String): InetAddress {
|
val m = InetAddress::class.java.getMethod("parseNumericAddress", String::class.java)
|
||||||
return m.invoke(null, address) as InetAddress
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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!!)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -95,6 +95,7 @@ open class Wireguard : Protocol() {
|
|||||||
return WireguardConfig.build {
|
return WireguardConfig.build {
|
||||||
configWireguard(configData, configDataJson)
|
configWireguard(configData, configDataJson)
|
||||||
configSplitTunneling(config)
|
configSplitTunneling(config)
|
||||||
|
configAppSplitTunneling(config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +158,7 @@ open class Wireguard : Protocol() {
|
|||||||
if (tunFd == null) {
|
if (tunFd == null) {
|
||||||
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
throw VpnStartException("Create VPN interface: permission not granted or revoked")
|
||||||
}
|
}
|
||||||
Log.v(TAG, "Wg-go backend ${GoBackend.awgVersion()}")
|
Log.i(TAG, "awg-go backend ${GoBackend.awgVersion()}")
|
||||||
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
|
tunnelHandle = GoBackend.awgTurnOn(ifName, tunFd.detachFd(), config.toWgUserspaceString())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -37,8 +37,8 @@ open class WireguardConfig protected constructor(
|
|||||||
|
|
||||||
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
open fun appendPeerLine(sb: StringBuilder) = with(sb) {
|
||||||
appendLine("public_key=$publicKeyHex")
|
appendLine("public_key=$publicKeyHex")
|
||||||
routes.forEach { route ->
|
routes.filter { it.include }.forEach { route ->
|
||||||
appendLine("allowed_ip=$route")
|
appendLine("allowed_ip=${route.inetNetwork}")
|
||||||
}
|
}
|
||||||
appendLine("endpoint=$endpoint")
|
appendLine("endpoint=$endpoint")
|
||||||
if (persistentKeepalive != 0)
|
if (persistentKeepalive != 0)
|
||||||
|
|||||||
@@ -20,37 +20,35 @@ set(QT_ANDROID_MULTI_ABI_FORWARD_VARS "QT_NO_GLOBAL_APK_TARGET_PART_OF_ALL;CMAKE
|
|||||||
|
|
||||||
# We need to include qtprivate api's
|
# We need to include qtprivate api's
|
||||||
# As QAndroidBinder is not yet implemented with a public api
|
# As QAndroidBinder is not yet implemented with a public api
|
||||||
set(LIBS ${LIBS} Qt6::CorePrivate)
|
set(LIBS ${LIBS} Qt6::CorePrivate -ljnigraphics)
|
||||||
|
|
||||||
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
link_directories(${CMAKE_CURRENT_SOURCE_DIR}/platforms/android)
|
||||||
|
|
||||||
set(HEADERS ${HEADERS}
|
set(HEADERS ${HEADERS}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.h
|
${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/android_utils.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.h
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.h
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES ${SOURCES}
|
set(SOURCES ${SOURCES}
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/android_controller.cpp
|
${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/android_utils.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/android/authResultReceiver.cpp
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
${CMAKE_CURRENT_SOURCE_DIR}/protocols/android_vpnprotocol.cpp
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/core/installedAppsImageProvider.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
foreach(abi IN ITEMS ${QT_ANDROID_ABIS})
|
||||||
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
set_property(TARGET ${PROJECT} PROPERTY QT_ANDROID_EXTRA_LIBS
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-go.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/amneziawg/android/${abi}/libwg-quick.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libck-ovpn-plugin.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpn3.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/libovpnutil.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openvpn/android/${abi}/librsapss.so
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libcrypto_3.so
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl/android/${abi}/libssl_3.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/libssh/android/${abi}/libssh.so
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libcrypto_3.so
|
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/3rd-prebuilt/3rd-prebuilt/openssl3/android/${abi}/libssl_3.so
|
|
||||||
)
|
)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ set(SOURCES ${SOURCES}
|
|||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/iosglue.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QRCodeReaderBase.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/QtAppDelegate.mm
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/platforms/ios/MobileUtils.mm
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -108,16 +107,19 @@ target_sources(${PROJECT} PRIVATE
|
|||||||
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/Log.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/LogRecord.swift
|
||||||
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
${CLIENT_ROOT_DIR}/platforms/ios/ScreenProtection.swift
|
||||||
|
${CLIENT_ROOT_DIR}/platforms/ios/VPNCController.swift
|
||||||
)
|
)
|
||||||
|
|
||||||
target_sources(${PROJECT} PRIVATE
|
target_sources(${PROJECT} PRIVATE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||||
)
|
)
|
||||||
|
|
||||||
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
set_property(TARGET ${PROJECT} APPEND PROPERTY RESOURCE
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/AmneziaVPNLaunchScreen.storyboard
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/Media.xcassets
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/ios/app/PrivacyInfo.xcprivacy
|
||||||
)
|
)
|
||||||
|
|
||||||
add_subdirectory(ios/networkextension)
|
add_subdirectory(ios/networkextension)
|
||||||
|
|||||||
@@ -3,17 +3,15 @@
|
|||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "core/controllers/serverController.h"
|
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
|
: WireguardConfigurator(settings, serverController, true, parent)
|
||||||
AwgConfigurator::AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
|
||||||
: WireguardConfigurator(settings, true, parent)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString AwgConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString config = WireguardConfigurator::genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
|
QString config = WireguardConfigurator::createConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
|
||||||
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
QJsonObject jsonConfig = QJsonDocument::fromJson(config.toUtf8()).object();
|
||||||
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
QString awgConfig = jsonConfig.value(config_key::config).toString();
|
||||||
@@ -41,8 +39,8 @@ QString AwgConfigurator::genAwgConfig(const ServerCredentials &credentials, Dock
|
|||||||
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
jsonConfig[config_key::responsePacketMagicHeader] = configMap.value(config_key::responsePacketMagicHeader);
|
||||||
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
jsonConfig[config_key::underloadPacketMagicHeader] = configMap.value(config_key::underloadPacketMagicHeader);
|
||||||
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
jsonConfig[config_key::transportPacketMagicHeader] = configMap.value(config_key::transportPacketMagicHeader);
|
||||||
jsonConfig[config_key::mtu] = containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().
|
jsonConfig[config_key::mtu] =
|
||||||
value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
containerConfig.value(ProtocolProps::protoToString(Proto::Awg)).toObject().value(config_key::mtu).toString(protocols::awg::defaultMtu);
|
||||||
|
|
||||||
return QJsonDocument(jsonConfig).toJson();
|
return QJsonDocument(jsonConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ class AwgConfigurator : public WireguardConfigurator
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
AwgConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
AwgConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QString genAwgConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // AWGCONFIGURATOR_H
|
#endif // AWGCONFIGURATOR_H
|
||||||
|
|||||||
@@ -1,34 +1,33 @@
|
|||||||
#include "cloak_configurator.h"
|
#include "cloak_configurator.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
|
|
||||||
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
CloakConfigurator::CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
ConfiguratorBase(settings, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
QString CloakConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
ErrorCode e = ErrorCode::NoError;
|
QString cloakPublicKey =
|
||||||
ServerController serverController(m_settings);
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckPublicKeyPath, errorCode);
|
||||||
|
|
||||||
QString cloakPublicKey = serverController.getTextFileFromContainer(container, credentials,
|
|
||||||
amnezia::protocols::cloak::ckPublicKeyPath, &e);
|
|
||||||
cloakPublicKey.replace("\n", "");
|
cloakPublicKey.replace("\n", "");
|
||||||
|
|
||||||
QString cloakBypassUid = serverController.getTextFileFromContainer(container, credentials,
|
if (errorCode != ErrorCode::NoError) {
|
||||||
amnezia::protocols::cloak::ckBypassUidKeyPath, &e);
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString cloakBypassUid =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::cloak::ckBypassUidKeyPath, errorCode);
|
||||||
cloakBypassUid.replace("\n", "");
|
cloakBypassUid.replace("\n", "");
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode) *errorCode = e;
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,9 +44,8 @@ QString CloakConfigurator::genCloakConfig(const ServerCredentials &credentials,
|
|||||||
config.insert("RemoteHost", credentials.hostName);
|
config.insert("RemoteHost", credentials.hostName);
|
||||||
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
|
config.insert("RemotePort", "$CLOAK_SERVER_PORT");
|
||||||
|
|
||||||
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
|
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
|
||||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
// qDebug().noquote() << textCfg;
|
|
||||||
return textCfg;
|
return textCfg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,14 +7,14 @@
|
|||||||
|
|
||||||
using namespace amnezia;
|
using namespace amnezia;
|
||||||
|
|
||||||
class CloakConfigurator : ConfiguratorBase
|
class CloakConfigurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
CloakConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
CloakConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QString genCloakConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CLOAK_CONFIGURATOR_H
|
#endif // CLOAK_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -1,8 +1,26 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
|
|
||||||
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent)
|
ConfiguratorBase::ConfiguratorBase(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: QObject{parent},
|
: QObject { parent }, m_settings(settings), m_serverController(serverController)
|
||||||
m_settings(settings)
|
|
||||||
{
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConfiguratorBase::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
return protocolConfigString;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ConfiguratorBase::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
return protocolConfigString;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ConfiguratorBase::processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
protocolConfigString.replace("$PRIMARY_DNS", dns.first);
|
||||||
|
protocolConfigString.replace("$SECONDARY_DNS", dns.second);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,19 +3,31 @@
|
|||||||
|
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
class Settings;
|
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
class ConfiguratorBase : public QObject
|
class ConfiguratorBase : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ConfiguratorBase(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
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;
|
||||||
|
|
||||||
|
virtual QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
virtual QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
void processConfigWithDnsSettings(const QPair<QString, QString> &dns, QString &protocolConfigString);
|
||||||
|
|
||||||
std::shared_ptr<Settings> m_settings;
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<ServerController> m_serverController;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONFIGURATORBASE_H
|
#endif // CONFIGURATORBASE_H
|
||||||
|
|||||||
@@ -9,18 +9,18 @@
|
|||||||
#include <QUuid>
|
#include <QUuid>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent)
|
Ikev2Configurator::Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: ConfiguratorBase(settings, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials,
|
Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, ErrorCode *errorCode)
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
Ikev2Configurator::ConnectionData connData;
|
Ikev2Configurator::ConnectionData connData;
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
@@ -39,18 +39,14 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
|||||||
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
"--extKeyUsage serverAuth,clientAuth -8 \"%1\"")
|
||||||
.arg(connData.clientId);
|
.arg(connData.clientId);
|
||||||
|
|
||||||
ServerController serverController(m_settings);
|
errorCode = m_serverController->runContainerScript(credentials, container, scriptCreateCert);
|
||||||
ErrorCode e = serverController.runContainerScript(credentials, container, scriptCreateCert);
|
|
||||||
|
|
||||||
QString scriptExportCert = QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"")
|
QString scriptExportCert =
|
||||||
.arg(connData.password)
|
QString("pk12util -W \"%1\" -d sql:/etc/ipsec.d -n \"%2\" -o \"%3\"").arg(connData.password).arg(connData.clientId).arg(certFileName);
|
||||||
.arg(connData.clientId)
|
errorCode = m_serverController->runContainerScript(credentials, container, scriptExportCert);
|
||||||
.arg(certFileName);
|
|
||||||
e = serverController.runContainerScript(credentials, container, scriptExportCert);
|
|
||||||
|
|
||||||
connData.clientCert = serverController.getTextFileFromContainer(container, credentials, certFileName, &e);
|
connData.clientCert = m_serverController->getTextFileFromContainer(container, credentials, certFileName, errorCode);
|
||||||
connData.caCert =
|
connData.caCert = m_serverController->getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", errorCode);
|
||||||
serverController.getTextFileFromContainer(container, credentials, "/etc/ipsec.d/ca_cert_base64.p12", &e);
|
|
||||||
|
|
||||||
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
|
qDebug() << "Ikev2Configurator::ConnectionData client cert size:" << connData.clientCert.size();
|
||||||
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
|
qDebug() << "Ikev2Configurator::ConnectionData ca cert size:" << connData.caCert.size();
|
||||||
@@ -58,13 +54,13 @@ Ikev2Configurator::ConnectionData Ikev2Configurator::prepareIkev2Config(const Se
|
|||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Ikev2Configurator::genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode)
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
Q_UNUSED(containerConfig)
|
Q_UNUSED(containerConfig)
|
||||||
|
|
||||||
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
|
ConnectionData connData = prepareIkev2Config(credentials, container, errorCode);
|
||||||
if (errorCode && *errorCode) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
class Ikev2Configurator : ConfiguratorBase
|
class Ikev2Configurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
Ikev2Configurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
Ikev2Configurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData {
|
struct ConnectionData {
|
||||||
QByteArray clientCert; // p12 client cert
|
QByteArray clientCert; // p12 client cert
|
||||||
@@ -21,15 +21,15 @@ public:
|
|||||||
QString host; // host ip
|
QString host; // host ip
|
||||||
};
|
};
|
||||||
|
|
||||||
QString genIkev2Config(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
QString genIkev2Config(const ConnectionData &connData);
|
QString genIkev2Config(const ConnectionData &connData);
|
||||||
QString genMobileConfig(const ConnectionData &connData);
|
QString genMobileConfig(const ConnectionData &connData);
|
||||||
QString genStrongSwanConfig(const ConnectionData &connData);
|
QString genStrongSwanConfig(const ConnectionData &connData);
|
||||||
|
|
||||||
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
|
ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
|
||||||
DockerContainer container, ErrorCode *errorCode = nullptr);
|
DockerContainer container, ErrorCode &errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IKEV2_CONFIGURATOR_H
|
#endif // IKEV2_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -14,9 +14,9 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
@@ -24,74 +24,61 @@
|
|||||||
#include <openssl/rsa.h>
|
#include <openssl/rsa.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
OpenVpnConfigurator::OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
: ConfiguratorBase(settings, parent)
|
QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::prepareOpenVpnConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container, ErrorCode &errorCode)
|
||||||
ErrorCode *errorCode)
|
|
||||||
{
|
{
|
||||||
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
OpenVpnConfigurator::ConnectionData connData = OpenVpnConfigurator::createCertRequest();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
|
|
||||||
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
|
if (connData.privKey.isEmpty() || connData.request.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::OpenSslFailed;
|
||||||
*errorCode = ErrorCode::OpenSslFailed;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
|
QString reqFileName = QString("%1/%2.req").arg(amnezia::protocols::openvpn::clientsDirPath).arg(connData.clientId);
|
||||||
|
|
||||||
ServerController serverController(m_settings);
|
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
||||||
ErrorCode e = serverController.uploadTextFileToContainer(container, credentials, connData.request, reqFileName);
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (e) {
|
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
e = signCert(container, credentials, connData.clientId);
|
errorCode = signCert(container, credentials, connData.clientId);
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.caCert = serverController.getTextFileFromContainer(container, credentials,
|
connData.caCert =
|
||||||
amnezia::protocols::openvpn::caCertPath, &e);
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::caCertPath, errorCode);
|
||||||
connData.clientCert = serverController.getTextFileFromContainer(
|
connData.clientCert = m_serverController->getTextFileFromContainer(
|
||||||
container, credentials,
|
container, credentials, QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), errorCode);
|
||||||
QString("%1/%2.crt").arg(amnezia::protocols::openvpn::clientCertPath).arg(connData.clientId), &e);
|
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.taKey = serverController.getTextFileFromContainer(container, credentials,
|
connData.taKey = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::openvpn::taKeyPath, errorCode);
|
||||||
amnezia::protocols::openvpn::taKeyPath, &e);
|
|
||||||
|
|
||||||
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
if (connData.caCert.isEmpty() || connData.clientCert.isEmpty() || connData.taKey.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::SshScpFailureError;
|
||||||
*errorCode = ErrorCode::SshScpFailureError;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString OpenVpnConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
ServerController serverController(m_settings);
|
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
||||||
QString config =
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
serverController.replaceVars(amnezia::scriptData(ProtocolScriptType::openvpn_template, container),
|
|
||||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
|
||||||
|
|
||||||
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
ConnectionData connData = prepareOpenVpnConfig(credentials, container, errorCode);
|
||||||
if (errorCode && *errorCode) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,34 +100,35 @@ QString OpenVpnConfigurator::genOpenVpnConfig(const ServerCredentials &credentia
|
|||||||
QJsonObject jConfig;
|
QJsonObject jConfig;
|
||||||
jConfig[config_key::config] = config;
|
jConfig[config_key::config] = config;
|
||||||
|
|
||||||
clientId = connData.clientId;
|
jConfig[config_key::clientId] = connData.clientId;
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig, const int serverIndex)
|
QString OpenVpnConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
QString config = json[config_key::config].toString();
|
QString config = json[config_key::config].toString();
|
||||||
|
|
||||||
if (!m_settings->server(serverIndex).value(config_key::configVersion).toInt()) {
|
if (!isApiConfig) {
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
config.replace(regex, "");
|
config.replace(regex, "");
|
||||||
|
|
||||||
if (m_settings->routeMode() == Settings::VpnAllSites) {
|
if (!m_settings->isSitesSplitTunnelingEnabled()) {
|
||||||
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
config.append("\nredirect-gateway def1 ipv6 bypass-dhcp\n");
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
}
|
} else if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
||||||
if (m_settings->routeMode() == Settings::VpnOnlyForwardSites) {
|
|
||||||
|
|
||||||
// no redirect-gateway
|
// no redirect-gateway
|
||||||
}
|
} else if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
||||||
if (m_settings->routeMode() == Settings::VpnAllExceptSites) {
|
#ifndef Q_OS_ANDROID
|
||||||
#ifndef Q_OS_ANDROID
|
|
||||||
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
config.append("\nredirect-gateway ipv6 !ipv4 bypass-dhcp\n");
|
||||||
#endif
|
#endif
|
||||||
// Prevent ipv6 leak
|
// Prevent ipv6 leak
|
||||||
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
config.append("ifconfig-ipv6 fd15:53b6:dead::2/64 fd15:53b6:dead::1\n");
|
||||||
config.append("block-ipv6\n");
|
config.append("block-ipv6\n");
|
||||||
@@ -164,9 +152,12 @@ QString OpenVpnConfigurator::processConfigWithLocalSettings(QString jsonConfig,
|
|||||||
return QJsonDocument(json).toJson();
|
return QJsonDocument(json).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString OpenVpnConfigurator::processConfigWithExportSettings(QString jsonConfig)
|
QString OpenVpnConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
QJsonObject json = QJsonDocument::fromJson(jsonConfig.toUtf8()).object();
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
|
|
||||||
|
QJsonObject json = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
QString config = json[config_key::config].toString();
|
QString config = json[config_key::config].toString();
|
||||||
|
|
||||||
QRegularExpression regex("redirect-gateway.*");
|
QRegularExpression regex("redirect-gateway.*");
|
||||||
@@ -198,12 +189,10 @@ ErrorCode OpenVpnConfigurator::signCert(DockerContainer container, const ServerC
|
|||||||
.arg(ContainerProps::containerToString(container))
|
.arg(ContainerProps::containerToString(container))
|
||||||
.arg(clientId);
|
.arg(clientId);
|
||||||
|
|
||||||
ServerController serverController(m_settings);
|
|
||||||
QStringList scriptList { script_import, script_sign };
|
QStringList scriptList { script_import, script_sign };
|
||||||
QString script = serverController.replaceVars(scriptList.join("\n"),
|
QString script = m_serverController->replaceVars(scriptList.join("\n"), m_serverController->genVarsForScript(credentials, container));
|
||||||
serverController.genVarsForScript(credentials, container));
|
|
||||||
|
|
||||||
return serverController.runScript(credentials, script);
|
return m_serverController->runScript(credentials, script);
|
||||||
}
|
}
|
||||||
|
|
||||||
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
||||||
@@ -237,8 +226,8 @@ OpenVpnConfigurator::ConnectionData OpenVpnConfigurator::createCertRequest()
|
|||||||
|
|
||||||
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0);
|
X509_NAME_add_entry_by_txt(x509_name, "C", MBSTRING_ASC, (unsigned char *)"ORG", -1, -1, 0);
|
||||||
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0);
|
X509_NAME_add_entry_by_txt(x509_name, "O", MBSTRING_ASC, (unsigned char *)"", -1, -1, 0);
|
||||||
X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC,
|
X509_NAME_add_entry_by_txt(x509_name, "CN", MBSTRING_ASC, reinterpret_cast<unsigned char const *>(clientIdUtf8.data()),
|
||||||
reinterpret_cast<unsigned char const *>(clientIdUtf8.data()), clientIdUtf8.size(), -1, 0);
|
clientIdUtf8.size(), -1, 0);
|
||||||
|
|
||||||
// 4. set public key of x509 req
|
// 4. set public key of x509 req
|
||||||
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
ret = X509_REQ_set_pubkey(x509_req, pKey);
|
||||||
|
|||||||
@@ -7,37 +7,37 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
class OpenVpnConfigurator : ConfiguratorBase
|
class OpenVpnConfigurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
OpenVpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
OpenVpnConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData {
|
struct ConnectionData
|
||||||
|
{
|
||||||
QString clientId;
|
QString clientId;
|
||||||
QString request; // certificate request
|
QString request; // certificate request
|
||||||
QString privKey; // client private key
|
QString privKey; // client private key
|
||||||
QString clientCert; // client signed certificate
|
QString clientCert; // client signed certificate
|
||||||
QString caCert; // server certificate
|
QString caCert; // server certificate
|
||||||
QString taKey; // tls-auth key
|
QString taKey; // tls-auth key
|
||||||
QString host; // host ip
|
QString host; // host ip
|
||||||
};
|
};
|
||||||
|
|
||||||
QString genOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(QString jsonConfig, const int serverIndex);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
QString processConfigWithExportSettings(QString jsonConfig);
|
QString &protocolConfigString);
|
||||||
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
ErrorCode signCert(DockerContainer container,
|
QString &protocolConfigString);
|
||||||
const ServerCredentials &credentials, QString clientId);
|
|
||||||
|
|
||||||
static ConnectionData createCertRequest();
|
static ConnectionData createCertRequest();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials,
|
ConnectionData prepareOpenVpnConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, ErrorCode *errorCode = nullptr);
|
ErrorCode &errorCode);
|
||||||
|
ErrorCode signCert(DockerContainer container, const ServerCredentials &credentials, QString clientId);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // OPENVPN_CONFIGURATOR_H
|
#endif // OPENVPN_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
#include "shadowsocks_configurator.h"
|
#include "shadowsocks_configurator.h"
|
||||||
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QJsonObject>
|
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
#include "core/controllers/serverController.h"
|
||||||
|
|
||||||
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent):
|
ShadowSocksConfigurator::ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
ConfiguratorBase(settings, parent)
|
QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &credentials,
|
QString ShadowSocksConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, const QJsonObject &containerConfig, ErrorCode *errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
ErrorCode e = ErrorCode::NoError;
|
QString ssKey =
|
||||||
ServerController serverController(m_settings);
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::shadowsocks::ssKeyPath, errorCode);
|
||||||
|
|
||||||
QString ssKey = serverController.getTextFileFromContainer(container, credentials,
|
|
||||||
amnezia::protocols::shadowsocks::ssKeyPath, &e);
|
|
||||||
ssKey.replace("\n", "");
|
ssKey.replace("\n", "");
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode) *errorCode = e;
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,10 +32,9 @@ QString ShadowSocksConfigurator::genShadowSocksConfig(const ServerCredentials &c
|
|||||||
config.insert("timeout", 60);
|
config.insert("timeout", 60);
|
||||||
config.insert("method", "$SHADOWSOCKS_CIPHER");
|
config.insert("method", "$SHADOWSOCKS_CIPHER");
|
||||||
|
|
||||||
|
QString textCfg = m_serverController->replaceVars(QJsonDocument(config).toJson(),
|
||||||
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
QString textCfg = serverController.replaceVars(QJsonDocument(config).toJson(),
|
// qDebug().noquote() << textCfg;
|
||||||
serverController.genVarsForScript(credentials, container, containerConfig));
|
|
||||||
|
|
||||||
//qDebug().noquote() << textCfg;
|
|
||||||
return textCfg;
|
return textCfg;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
#include "configurator_base.h"
|
#include "configurator_base.h"
|
||||||
#include "core/defs.h"
|
#include "core/defs.h"
|
||||||
|
|
||||||
class ShadowSocksConfigurator : ConfiguratorBase
|
class ShadowSocksConfigurator : public ConfiguratorBase
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
ShadowSocksConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QString genShadowSocksConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
#endif // SHADOWSOCKS_CONFIGURATOR_H
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
SshConfigurator::SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
: ConfiguratorBase(settings, parent)
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,8 +82,7 @@ void SshConfigurator::openSshTerminal(const ServerCredentials &credentials)
|
|||||||
// p->setNativeArguments(QString("%1@%2")
|
// p->setNativeArguments(QString("%1@%2")
|
||||||
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
// .arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||||
} else {
|
} else {
|
||||||
p->setNativeArguments(
|
p->setNativeArguments(QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
||||||
QString("%1@%2 -pw %3").arg(credentials.userName).arg(credentials.hostName).arg(credentials.secretData));
|
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
p->setProgram("/bin/bash");
|
p->setProgram("/bin/bash");
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class SshConfigurator : ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
SshConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
SshConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
QProcessEnvironment prepareEnv();
|
QProcessEnvironment prepareEnv();
|
||||||
QString convertOpenSShKey(const QString &key);
|
QString convertOpenSShKey(const QString &key);
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
#include "vpn_configurator.h"
|
|
||||||
#include "cloak_configurator.h"
|
|
||||||
#include "ikev2_configurator.h"
|
|
||||||
#include "openvpn_configurator.h"
|
|
||||||
#include "shadowsocks_configurator.h"
|
|
||||||
#include "ssh_configurator.h"
|
|
||||||
#include "wireguard_configurator.h"
|
|
||||||
#include "awg_configurator.h"
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QJsonDocument>
|
|
||||||
#include <QJsonObject>
|
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
|
||||||
#include "settings.h"
|
|
||||||
#include "utilities.h"
|
|
||||||
|
|
||||||
VpnConfigurator::VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent)
|
|
||||||
: ConfiguratorBase(settings, parent)
|
|
||||||
{
|
|
||||||
openVpnConfigurator = std::shared_ptr<OpenVpnConfigurator>(new OpenVpnConfigurator(settings, this));
|
|
||||||
shadowSocksConfigurator = std::shared_ptr<ShadowSocksConfigurator>(new ShadowSocksConfigurator(settings, this));
|
|
||||||
cloakConfigurator = std::shared_ptr<CloakConfigurator>(new CloakConfigurator(settings, this));
|
|
||||||
wireguardConfigurator = std::shared_ptr<WireguardConfigurator>(new WireguardConfigurator(settings, false, this));
|
|
||||||
ikev2Configurator = std::shared_ptr<Ikev2Configurator>(new Ikev2Configurator(settings, this));
|
|
||||||
sshConfigurator = std::shared_ptr<SshConfigurator>(new SshConfigurator(settings, this));
|
|
||||||
awgConfigurator = std::shared_ptr<AwgConfigurator>(new AwgConfigurator(settings, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
|
||||||
const QJsonObject &containerConfig, Proto proto, QString &clientId, ErrorCode *errorCode)
|
|
||||||
{
|
|
||||||
switch (proto) {
|
|
||||||
case Proto::OpenVpn:
|
|
||||||
return openVpnConfigurator->genOpenVpnConfig(credentials, container, containerConfig, clientId, errorCode);
|
|
||||||
|
|
||||||
case Proto::ShadowSocks:
|
|
||||||
return shadowSocksConfigurator->genShadowSocksConfig(credentials, container, containerConfig, errorCode);
|
|
||||||
|
|
||||||
case Proto::Cloak: return cloakConfigurator->genCloakConfig(credentials, container, containerConfig, errorCode);
|
|
||||||
|
|
||||||
case Proto::WireGuard:
|
|
||||||
return wireguardConfigurator->genWireguardConfig(credentials, container, containerConfig, clientId, errorCode);
|
|
||||||
|
|
||||||
case Proto::Awg:
|
|
||||||
return awgConfigurator->genAwgConfig(credentials, container, containerConfig, clientId, errorCode);
|
|
||||||
|
|
||||||
case Proto::Ikev2: return ikev2Configurator->genIkev2Config(credentials, container, containerConfig, errorCode);
|
|
||||||
|
|
||||||
default: return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
|
|
||||||
{
|
|
||||||
QPair<QString, QString> dns;
|
|
||||||
|
|
||||||
bool useAmneziaDns = m_settings->useAmneziaDns();
|
|
||||||
const QJsonObject &server = m_settings->server(serverIndex);
|
|
||||||
|
|
||||||
dns.first = server.value(config_key::dns1).toString();
|
|
||||||
dns.second = server.value(config_key::dns2).toString();
|
|
||||||
|
|
||||||
if (dns.first.isEmpty() || !Utils::checkIPv4Format(dns.first)) {
|
|
||||||
if (useAmneziaDns && m_settings->containers(serverIndex).contains(DockerContainer::Dns)) {
|
|
||||||
dns.first = protocols::dns::amneziaDnsIp;
|
|
||||||
} else
|
|
||||||
dns.first = m_settings->primaryDns();
|
|
||||||
}
|
|
||||||
if (dns.second.isEmpty() || !Utils::checkIPv4Format(dns.second)) {
|
|
||||||
dns.second = m_settings->secondaryDns();
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "VpnConfigurator::getDnsForConfig" << dns.first << dns.second;
|
|
||||||
return dns;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &VpnConfigurator::processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto,
|
|
||||||
QString &config)
|
|
||||||
{
|
|
||||||
auto dns = getDnsForConfig(serverIndex);
|
|
||||||
|
|
||||||
config.replace("$PRIMARY_DNS", dns.first);
|
|
||||||
config.replace("$SECONDARY_DNS", dns.second);
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &VpnConfigurator::processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto,
|
|
||||||
QString &config)
|
|
||||||
{
|
|
||||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
|
||||||
|
|
||||||
if (proto == Proto::OpenVpn) {
|
|
||||||
config = openVpnConfigurator->processConfigWithLocalSettings(config, serverIndex);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto,
|
|
||||||
QString &config)
|
|
||||||
{
|
|
||||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
|
||||||
|
|
||||||
if (proto == Proto::OpenVpn) {
|
|
||||||
config = openVpnConfigurator->processConfigWithExportSettings(config);
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VpnConfigurator::updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
|
|
||||||
const QString &stdOut)
|
|
||||||
{
|
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
|
||||||
|
|
||||||
if (container == DockerContainer::TorWebSite) {
|
|
||||||
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
|
||||||
|
|
||||||
qDebug() << "amnezia-tor onions" << stdOut;
|
|
||||||
|
|
||||||
QString onion = stdOut;
|
|
||||||
onion.replace("\n", "");
|
|
||||||
protocol.insert(config_key::site, onion);
|
|
||||||
|
|
||||||
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
#ifndef VPN_CONFIGURATOR_H
|
|
||||||
#define VPN_CONFIGURATOR_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
#include "configurator_base.h"
|
|
||||||
#include "core/defs.h"
|
|
||||||
|
|
||||||
class OpenVpnConfigurator;
|
|
||||||
class ShadowSocksConfigurator;
|
|
||||||
class CloakConfigurator;
|
|
||||||
class WireguardConfigurator;
|
|
||||||
class Ikev2Configurator;
|
|
||||||
class SshConfigurator;
|
|
||||||
class AwgConfigurator;
|
|
||||||
|
|
||||||
// Retrieve connection settings from server
|
|
||||||
class VpnConfigurator : public ConfiguratorBase
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit VpnConfigurator(std::shared_ptr<Settings> settings, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
|
||||||
const QJsonObject &containerConfig, Proto proto, QString &clientId,
|
|
||||||
ErrorCode *errorCode = nullptr);
|
|
||||||
|
|
||||||
QPair<QString, QString> getDnsForConfig(int serverIndex);
|
|
||||||
QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
|
||||||
|
|
||||||
QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
|
||||||
QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
|
||||||
|
|
||||||
// workaround for containers which is not support normal configuration
|
|
||||||
void updateContainerConfigAfterInstallation(DockerContainer container, QJsonObject &containerConfig,
|
|
||||||
const QString &stdOut);
|
|
||||||
|
|
||||||
std::shared_ptr<OpenVpnConfigurator> openVpnConfigurator;
|
|
||||||
std::shared_ptr<ShadowSocksConfigurator> shadowSocksConfigurator;
|
|
||||||
std::shared_ptr<CloakConfigurator> cloakConfigurator;
|
|
||||||
std::shared_ptr<WireguardConfigurator> wireguardConfigurator;
|
|
||||||
std::shared_ptr<Ikev2Configurator> ikev2Configurator;
|
|
||||||
std::shared_ptr<SshConfigurator> sshConfigurator;
|
|
||||||
std::shared_ptr<AwgConfigurator> awgConfigurator;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void newVpnConfigCreated(const QString &clientId, const QString &clientName, const DockerContainer container,
|
|
||||||
ServerCredentials credentials);
|
|
||||||
void clientModelUpdated();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // VPN_CONFIGURATOR_H
|
|
||||||
@@ -13,23 +13,20 @@
|
|||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
#include "core/controllers/serverController.h"
|
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
|
||||||
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent)
|
WireguardConfigurator::WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController,
|
||||||
: ConfiguratorBase(settings, parent), m_isAwg(isAwg)
|
bool isAwg, QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent), m_isAwg(isAwg)
|
||||||
{
|
{
|
||||||
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath
|
m_serverConfigPath = m_isAwg ? amnezia::protocols::awg::serverConfigPath : amnezia::protocols::wireguard::serverConfigPath;
|
||||||
: amnezia::protocols::wireguard::serverConfigPath;
|
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath : amnezia::protocols::wireguard::serverPublicKeyPath;
|
||||||
m_serverPublicKeyPath = m_isAwg ? amnezia::protocols::awg::serverPublicKeyPath
|
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath : amnezia::protocols::wireguard::serverPskKeyPath;
|
||||||
: amnezia::protocols::wireguard::serverPublicKeyPath;
|
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template : ProtocolScriptType::wireguard_template;
|
||||||
m_serverPskKeyPath = m_isAwg ? amnezia::protocols::awg::serverPskKeyPath
|
|
||||||
: amnezia::protocols::wireguard::serverPskKeyPath;
|
|
||||||
m_configTemplate = m_isAwg ? ProtocolScriptType::awg_template
|
|
||||||
: ProtocolScriptType::wireguard_template;
|
|
||||||
|
|
||||||
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
m_protocolName = m_isAwg ? config_key::awg : config_key::wireguard;
|
||||||
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
m_defaultPort = m_isAwg ? protocols::wireguard::defaultPort : protocols::awg::defaultPort;
|
||||||
@@ -68,22 +65,17 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::genClientKeys()
|
|||||||
|
|
||||||
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardConfig(const ServerCredentials &credentials,
|
||||||
DockerContainer container,
|
DockerContainer container,
|
||||||
const QJsonObject &containerConfig,
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
ErrorCode *errorCode)
|
|
||||||
{
|
{
|
||||||
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
WireguardConfigurator::ConnectionData connData = WireguardConfigurator::genClientKeys();
|
||||||
connData.host = credentials.hostName;
|
connData.host = credentials.hostName;
|
||||||
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
|
connData.port = containerConfig.value(m_protocolName).toObject().value(config_key::port).toString(m_defaultPort);
|
||||||
|
|
||||||
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
if (connData.clientPrivKey.isEmpty() || connData.clientPubKey.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::InternalError;
|
||||||
*errorCode = ErrorCode::InternalError;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode e = ErrorCode::NoError;
|
|
||||||
ServerController serverController(m_settings);
|
|
||||||
|
|
||||||
// Get list of already created clients (only IP addresses)
|
// Get list of already created clients (only IP addresses)
|
||||||
QString nextIpNumber;
|
QString nextIpNumber;
|
||||||
{
|
{
|
||||||
@@ -94,9 +86,8 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
e = serverController.runContainerScript(credentials, container, script, cbReadStdOut);
|
errorCode = m_serverController->runContainerScript(credentials, container, script, cbReadStdOut);
|
||||||
if (errorCode && e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,21 +101,18 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
} else {
|
} else {
|
||||||
int next = ips.last().split(".").last().toInt() + 1;
|
int next = ips.last().split(".").last().toInt() + 1;
|
||||||
if (next > 254) {
|
if (next > 254) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::AddressPoolError;
|
||||||
*errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
nextIpNumber = QString::number(next);
|
nextIpNumber = QString::number(next);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString subnetIp =
|
QString subnetIp = containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
||||||
containerConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress);
|
|
||||||
{
|
{
|
||||||
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
QStringList l = subnetIp.split(".", Qt::SkipEmptyParts);
|
||||||
if (l.isEmpty()) {
|
if (l.isEmpty()) {
|
||||||
if (errorCode)
|
errorCode = ErrorCode::AddressPoolError;
|
||||||
*errorCode = ErrorCode::AddressPoolError;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
l.removeLast();
|
l.removeLast();
|
||||||
@@ -134,20 +122,16 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get keys
|
// Get keys
|
||||||
connData.serverPubKey = serverController.getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, &e);
|
connData.serverPubKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPublicKeyPath, errorCode);
|
||||||
connData.serverPubKey.replace("\n", "");
|
connData.serverPubKey.replace("\n", "");
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
connData.pskKey = serverController.getTextFileFromContainer(container, credentials, m_serverPskKeyPath, &e);
|
connData.pskKey = m_serverController->getTextFileFromContainer(container, credentials, m_serverPskKeyPath, errorCode);
|
||||||
connData.pskKey.replace("\n", "");
|
connData.pskKey.replace("\n", "");
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,34 +142,30 @@ WireguardConfigurator::ConnectionData WireguardConfigurator::prepareWireguardCon
|
|||||||
"AllowedIPs = %3/32\n\n")
|
"AllowedIPs = %3/32\n\n")
|
||||||
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
.arg(connData.clientPubKey, connData.pskKey, connData.clientIP);
|
||||||
|
|
||||||
e = serverController.uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
errorCode = m_serverController->uploadTextFileToContainer(container, credentials, configPart, m_serverConfigPath,
|
||||||
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
libssh::ScpOverwriteMode::ScpAppendToExisting);
|
||||||
|
|
||||||
if (e) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'")
|
QString script = QString("sudo docker exec -i $CONTAINER_NAME bash -c 'wg syncconf wg0 <(wg-quick strip %1)'").arg(m_serverConfigPath);
|
||||||
.arg(m_serverConfigPath);
|
|
||||||
|
|
||||||
e = serverController.runScript(
|
errorCode = m_serverController->runScript(
|
||||||
credentials, serverController.replaceVars(script, serverController.genVarsForScript(credentials, container)));
|
credentials, m_serverController->replaceVars(script, m_serverController->genVarsForScript(credentials, container)));
|
||||||
|
|
||||||
return connData;
|
return connData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString WireguardConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode)
|
const QJsonObject &containerConfig, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
ServerController serverController(m_settings);
|
|
||||||
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
QString scriptData = amnezia::scriptData(m_configTemplate, container);
|
||||||
QString config = serverController.replaceVars(
|
QString config =
|
||||||
scriptData, serverController.genVarsForScript(credentials, container, containerConfig));
|
m_serverController->replaceVars(scriptData, m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
ConnectionData connData = prepareWireguardConfig(credentials, container, containerConfig, errorCode);
|
||||||
if (errorCode && *errorCode) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -205,30 +185,25 @@ QString WireguardConfigurator::genWireguardConfig(const ServerCredentials &crede
|
|||||||
jConfig[config_key::client_pub_key] = connData.clientPubKey;
|
jConfig[config_key::client_pub_key] = connData.clientPubKey;
|
||||||
jConfig[config_key::psk_key] = connData.pskKey;
|
jConfig[config_key::psk_key] = connData.pskKey;
|
||||||
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
jConfig[config_key::server_pub_key] = connData.serverPubKey;
|
||||||
|
|
||||||
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
jConfig[config_key::mtu] = wireguarConfig.value(config_key::mtu).toString(protocols::wireguard::defaultMtu);
|
||||||
|
|
||||||
clientId = connData.clientPubKey;
|
jConfig[config_key::clientId] = connData.clientPubKey;
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
return QJsonDocument(jConfig).toJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithLocalSettings(QString config)
|
QString WireguardConfigurator::processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
// TODO replace DNS if it already set
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
|
|
||||||
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
|
|
||||||
|
|
||||||
QJsonObject jConfig;
|
return protocolConfigString;
|
||||||
jConfig[config_key::config] = config;
|
|
||||||
|
|
||||||
return QJsonDocument(jConfig).toJson();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString WireguardConfigurator::processConfigWithExportSettings(QString config)
|
QString WireguardConfigurator::processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig,
|
||||||
|
QString &protocolConfigString)
|
||||||
{
|
{
|
||||||
config.replace("$PRIMARY_DNS", m_settings->primaryDns());
|
processConfigWithDnsSettings(dns, protocolConfigString);
|
||||||
config.replace("$SECONDARY_DNS", m_settings->secondaryDns());
|
|
||||||
|
|
||||||
return config;
|
return protocolConfigString;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ class WireguardConfigurator : public ConfiguratorBase
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
WireguardConfigurator(std::shared_ptr<Settings> settings, bool isAwg, QObject *parent = nullptr);
|
WireguardConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, bool isAwg,
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
struct ConnectionData
|
struct ConnectionData
|
||||||
{
|
{
|
||||||
@@ -25,18 +26,18 @@ public:
|
|||||||
QString port;
|
QString port;
|
||||||
};
|
};
|
||||||
|
|
||||||
QString genWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
QString createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
const QJsonObject &containerConfig, QString &clientId, ErrorCode *errorCode = nullptr);
|
ErrorCode &errorCode);
|
||||||
|
|
||||||
QString processConfigWithLocalSettings(QString config);
|
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||||
QString processConfigWithExportSettings(QString config);
|
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
|
||||||
|
|
||||||
static ConnectionData genClientKeys();
|
static ConnectionData genClientKeys();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
ConnectionData prepareWireguardConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &containerConfig, ErrorCode *errorCode = nullptr);
|
const QJsonObject &containerConfig, ErrorCode &errorCode);
|
||||||
|
|
||||||
bool m_isAwg;
|
bool m_isAwg;
|
||||||
QString m_serverConfigPath;
|
QString m_serverConfigPath;
|
||||||
QString m_serverPublicKeyPath;
|
QString m_serverPublicKeyPath;
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
#include "xray_configurator.h"
|
||||||
|
|
||||||
|
#include <QFile>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
|
||||||
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/controllers/serverController.h"
|
||||||
|
#include "core/scripts_registry.h"
|
||||||
|
|
||||||
|
XrayConfigurator::XrayConfigurator(std::shared_ptr<Settings> settings, const QSharedPointer<ServerController> &serverController, QObject *parent)
|
||||||
|
: ConfiguratorBase(settings, serverController, parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QString XrayConfigurator::createConfig(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &containerConfig,
|
||||||
|
ErrorCode &errorCode)
|
||||||
|
{
|
||||||
|
QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::xray_template, container),
|
||||||
|
m_serverController->genVarsForScript(credentials, container, containerConfig));
|
||||||
|
|
||||||
|
QString xrayPublicKey =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::PublicKeyPath, errorCode);
|
||||||
|
xrayPublicKey.replace("\n", "");
|
||||||
|
|
||||||
|
QString xrayUuid = m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::uuidPath, errorCode);
|
||||||
|
xrayUuid.replace("\n", "");
|
||||||
|
|
||||||
|
QString xrayShortId =
|
||||||
|
m_serverController->getTextFileFromContainer(container, credentials, amnezia::protocols::xray::shortidPath, errorCode);
|
||||||
|
xrayShortId.replace("\n", "");
|
||||||
|
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
config.replace("$XRAY_CLIENT_ID", xrayUuid);
|
||||||
|
config.replace("$XRAY_PUBLIC_KEY", xrayPublicKey);
|
||||||
|
config.replace("$XRAY_SHORT_ID", xrayShortId);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef XRAY_CONFIGURATOR_H
|
||||||
|
#define XRAY_CONFIGURATOR_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "configurator_base.h"
|
||||||
|
#include "core/defs.h"
|
||||||
|
|
||||||
|
class XrayConfigurator : public ConfiguratorBase
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // XRAY_CONFIGURATOR_H
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
#include "containers_defs.h"
|
#include "containers_defs.h"
|
||||||
|
|
||||||
|
#include "QJsonObject"
|
||||||
|
#include "QJsonDocument"
|
||||||
|
|
||||||
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
QDebug operator<<(QDebug debug, const amnezia::DockerContainer &c)
|
||||||
{
|
{
|
||||||
QDebugStateSaver saver(debug);
|
QDebugStateSaver saver(debug);
|
||||||
@@ -58,6 +61,10 @@ QVector<amnezia::Proto> ContainerProps::protocolsForContainer(amnezia::DockerCon
|
|||||||
|
|
||||||
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
case DockerContainer::Ipsec: return { Proto::Ikev2 /*, Protocol::L2tp */ };
|
||||||
|
|
||||||
|
case DockerContainer::Xray: return { Proto::Xray };
|
||||||
|
|
||||||
|
case DockerContainer::SSXray: return { Proto::SSXray };
|
||||||
|
|
||||||
case DockerContainer::Dns: return { Proto::Dns };
|
case DockerContainer::Dns: return { Proto::Dns };
|
||||||
|
|
||||||
case DockerContainer::Sftp: return { Proto::Sftp };
|
case DockerContainer::Sftp: return { Proto::Sftp };
|
||||||
@@ -85,7 +92,9 @@ QMap<DockerContainer, QString> ContainerProps::containerHumanNames()
|
|||||||
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
{ DockerContainer::Cloak, "OpenVPN over Cloak" },
|
||||||
{ DockerContainer::WireGuard, "WireGuard" },
|
{ DockerContainer::WireGuard, "WireGuard" },
|
||||||
{ DockerContainer::Awg, "AmneziaWG" },
|
{ DockerContainer::Awg, "AmneziaWG" },
|
||||||
|
{ DockerContainer::Xray, "XRay" },
|
||||||
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
{ DockerContainer::Ipsec, QObject::tr("IPsec") },
|
||||||
|
{ DockerContainer::SSXray, "ShadowSocks"},
|
||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
{ DockerContainer::Dns, QObject::tr("Amnezia DNS") },
|
||||||
@@ -111,6 +120,9 @@ QMap<DockerContainer, QString> ContainerProps::containerDescriptions()
|
|||||||
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
QObject::tr("AmneziaWG - Special protocol from Amnezia, based on WireGuard. It's fast like WireGuard, "
|
||||||
"but very resistant to blockages. "
|
"but very resistant to blockages. "
|
||||||
"Recommended for regions with high levels of censorship.") },
|
"Recommended for regions with high levels of censorship.") },
|
||||||
|
{ DockerContainer::Xray,
|
||||||
|
QObject::tr("XRay with REALITY - Suitable for countries with the highest level of internet censorship. "
|
||||||
|
"Traffic masking as web traffic at the TLS level, and protection against detection by active probing methods.") },
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
QObject::tr("IKEv2 - Modern stable protocol, a bit faster than others, restores connection after "
|
||||||
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
"signal loss. It has native support on the latest versions of Android and iOS.") },
|
||||||
@@ -199,6 +211,17 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
"* Minimum number of settings\n"
|
"* Minimum number of settings\n"
|
||||||
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
"* Not recognised by DPI analysis systems, resistant to blocking\n"
|
||||||
"* Works over UDP network protocol.") },
|
"* Works over UDP network protocol.") },
|
||||||
|
{ DockerContainer::Xray,
|
||||||
|
QObject::tr("The REALITY protocol, a pioneering development by the creators of XRay, "
|
||||||
|
"is specifically designed to counteract the highest levels of internet censorship through its novel approach to evasion.\n"
|
||||||
|
"It uniquely identifies censors during the TLS handshake phase, seamlessly operating as a proxy for legitimate clients while diverting censors to genuine websites like google.com, "
|
||||||
|
"thus presenting an authentic TLS certificate and data. \n"
|
||||||
|
"This advanced capability differentiates REALITY from similar technologies by its ability to disguise web traffic as coming from random, "
|
||||||
|
"legitimate sites without the need for specific configurations. \n"
|
||||||
|
"Unlike older protocols such as VMess, VLESS, and the XTLS-Vision transport, "
|
||||||
|
"REALITY's innovative \"friend or foe\" recognition at the TLS handshake enhances security and circumvents detection by sophisticated DPI systems employing active probing techniques. "
|
||||||
|
"This makes REALITY a robust solution for maintaining internet freedom in environments with stringent censorship.")
|
||||||
|
},
|
||||||
{ DockerContainer::Ipsec,
|
{ DockerContainer::Ipsec,
|
||||||
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
QObject::tr("IKEv2, paired with the IPSec encryption layer, stands as a modern and stable VPN protocol.\n"
|
||||||
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
"One of its distinguishing features is its ability to swiftly switch between networks and devices, "
|
||||||
@@ -213,7 +236,11 @@ QMap<DockerContainer, QString> ContainerProps::containerDetailedDescriptions()
|
|||||||
|
|
||||||
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
{ DockerContainer::TorWebSite, QObject::tr("Website in Tor network") },
|
||||||
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
{ DockerContainer::Dns, QObject::tr("DNS Service") },
|
||||||
{ DockerContainer::Sftp, QObject::tr("Sftp file sharing service - is secure FTP service") }
|
{ DockerContainer::Sftp,
|
||||||
|
QObject::tr("After installation, Amnezia will create a\n\n file storage on your server. "
|
||||||
|
"You will be able to access it using\n FileZilla or other SFTP clients, "
|
||||||
|
"as well as mount the disk on your device to access\n it directly from your device.\n\n"
|
||||||
|
"For more detailed information, you can\n find it in the support section under \"Create SFTP file storage.\" ") }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,7 +258,9 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
|||||||
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
case DockerContainer::ShadowSocks: return Proto::ShadowSocks;
|
||||||
case DockerContainer::WireGuard: return Proto::WireGuard;
|
case DockerContainer::WireGuard: return Proto::WireGuard;
|
||||||
case DockerContainer::Awg: return Proto::Awg;
|
case DockerContainer::Awg: return Proto::Awg;
|
||||||
|
case DockerContainer::Xray: return Proto::Xray;
|
||||||
case DockerContainer::Ipsec: return Proto::Ikev2;
|
case DockerContainer::Ipsec: return Proto::Ikev2;
|
||||||
|
case DockerContainer::SSXray: return Proto::SSXray;
|
||||||
|
|
||||||
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
case DockerContainer::TorWebSite: return Proto::TorWebSite;
|
||||||
case DockerContainer::Dns: return Proto::Dns;
|
case DockerContainer::Dns: return Proto::Dns;
|
||||||
@@ -274,7 +303,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
|||||||
|
|
||||||
#elif defined(Q_OS_LINUX)
|
#elif defined(Q_OS_LINUX)
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case DockerContainer::WireGuard: return true;
|
|
||||||
case DockerContainer::Ipsec: return false;
|
case DockerContainer::Ipsec: return false;
|
||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
@@ -297,7 +325,7 @@ bool ContainerProps::isEasySetupContainer(DockerContainer container)
|
|||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return true;
|
case DockerContainer::WireGuard: return true;
|
||||||
case DockerContainer::Awg: return true;
|
case DockerContainer::Awg: return true;
|
||||||
case DockerContainer::Cloak: return true;
|
// case DockerContainer::Cloak: return true;
|
||||||
default: return false;
|
default: return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -306,8 +334,8 @@ QString ContainerProps::easySetupHeader(DockerContainer container)
|
|||||||
{
|
{
|
||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return tr("Low");
|
case DockerContainer::WireGuard: return tr("Low");
|
||||||
case DockerContainer::Awg: return tr("Medium or High");
|
case DockerContainer::Awg: return tr("High");
|
||||||
case DockerContainer::Cloak: return tr("Extreme");
|
// case DockerContainer::Cloak: return tr("Extreme");
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -317,8 +345,8 @@ QString ContainerProps::easySetupDescription(DockerContainer container)
|
|||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return tr("I just want to increase the level of my privacy.");
|
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::Awg: return tr("I want to bypass censorship. This option recommended in most cases.");
|
||||||
case DockerContainer::Cloak:
|
// case DockerContainer::Cloak:
|
||||||
return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
// return tr("Most VPN protocols are blocked. Recommended if other options are not working.");
|
||||||
default: return "";
|
default: return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -328,7 +356,7 @@ int ContainerProps::easySetupOrder(DockerContainer container)
|
|||||||
switch (container) {
|
switch (container) {
|
||||||
case DockerContainer::WireGuard: return 3;
|
case DockerContainer::WireGuard: return 3;
|
||||||
case DockerContainer::Awg: return 2;
|
case DockerContainer::Awg: return 2;
|
||||||
case DockerContainer::Cloak: return 1;
|
// case DockerContainer::Cloak: return 1;
|
||||||
default: return 0;
|
default: return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -342,3 +370,13 @@ bool ContainerProps::isShareable(DockerContainer container)
|
|||||||
default: return true;
|
default: return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject ContainerProps::getProtocolConfigFromContainer(const Proto protocol, const QJsonObject &containerConfig)
|
||||||
|
{
|
||||||
|
QString protocolConfigString = containerConfig.value(ProtocolProps::protoToString(protocol))
|
||||||
|
.toObject()
|
||||||
|
.value(config_key::last_config)
|
||||||
|
.toString();
|
||||||
|
|
||||||
|
return QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ namespace amnezia
|
|||||||
Cloak,
|
Cloak,
|
||||||
ShadowSocks,
|
ShadowSocks,
|
||||||
Ipsec,
|
Ipsec,
|
||||||
|
Xray,
|
||||||
|
SSXray,
|
||||||
|
|
||||||
// non-vpn
|
// non-vpn
|
||||||
TorWebSite,
|
TorWebSite,
|
||||||
@@ -67,6 +69,8 @@ namespace amnezia
|
|||||||
static int easySetupOrder(amnezia::DockerContainer container);
|
static int easySetupOrder(amnezia::DockerContainer container);
|
||||||
|
|
||||||
static bool isShareable(amnezia::DockerContainer container);
|
static bool isShareable(amnezia::DockerContainer container);
|
||||||
|
|
||||||
|
static QJsonObject getProtocolConfigFromContainer(const amnezia::Proto protocol, const QJsonObject &containerConfig);
|
||||||
};
|
};
|
||||||
|
|
||||||
static void declareQmlContainerEnum()
|
static void declareQmlContainerEnum()
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
#include "apiController.h"
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
|
||||||
|
#include "amnezia_application.h"
|
||||||
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
namespace configKey
|
||||||
|
{
|
||||||
|
constexpr char cloak[] = "cloak";
|
||||||
|
constexpr char awg[] = "awg";
|
||||||
|
|
||||||
|
constexpr char apiEdnpoint[] = "api_endpoint";
|
||||||
|
constexpr char accessToken[] = "api_key";
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiController::ApiController(QObject *parent) : QObject(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ApiController::processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config)
|
||||||
|
{
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
config.replace("<key>", "<key>\n");
|
||||||
|
config.replace("$OPENVPN_PRIV_KEY", apiPayloadData.certRequest.privKey);
|
||||||
|
return;
|
||||||
|
} else if (protocol == configKey::awg) {
|
||||||
|
config.replace("$WIREGUARD_CLIENT_PRIVATE_KEY", apiPayloadData.wireGuardClientPrivKey);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiController::ApiPayloadData ApiController::generateApiPayloadData(const QString &protocol)
|
||||||
|
{
|
||||||
|
ApiController::ApiPayloadData apiPayload;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
apiPayload.certRequest = OpenVpnConfigurator::createCertRequest();
|
||||||
|
} else if (protocol == configKey::awg) {
|
||||||
|
auto connData = WireguardConfigurator::genClientKeys();
|
||||||
|
apiPayload.wireGuardClientPubKey = connData.clientPubKey;
|
||||||
|
apiPayload.wireGuardClientPrivKey = connData.clientPrivKey;
|
||||||
|
}
|
||||||
|
return apiPayload;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject ApiController::fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData)
|
||||||
|
{
|
||||||
|
QJsonObject obj;
|
||||||
|
if (protocol == configKey::cloak) {
|
||||||
|
obj[configKey::certificate] = apiPayloadData.certRequest.request;
|
||||||
|
} 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)
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_IOS
|
||||||
|
IosController::Instance()->requestInetAccess();
|
||||||
|
QThread::msleep(10);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
QString protocol = serverConfig.value(configKey::protocol).toString();
|
||||||
|
|
||||||
|
ApiPayloadData apiPayloadData = generateApiPayloadData(protocol);
|
||||||
|
|
||||||
|
QJsonObject apiPayload = fillApiPayload(protocol, apiPayloadData);
|
||||||
|
apiPayload[configKey::uuid] = installationUuid;
|
||||||
|
|
||||||
|
QByteArray requestBody = QJsonDocument(apiPayload).toJson();
|
||||||
|
|
||||||
|
QNetworkReply *reply = amnApp->manager()->post(request, requestBody); // ??
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
data.replace("vpn://", "");
|
||||||
|
QByteArray ba = QByteArray::fromBase64(data.toUtf8(), QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
|
||||||
|
if (ba.isEmpty()) {
|
||||||
|
emit errorOccurred(ErrorCode::ApiConfigEmptyError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray ba_uncompressed = qUncompress(ba);
|
||||||
|
if (!ba_uncompressed.isEmpty()) {
|
||||||
|
ba = ba_uncompressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString configStr = ba;
|
||||||
|
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);
|
||||||
|
|
||||||
|
auto defaultContainer = apiConfig.value(config_key::defaultContainer).toString();
|
||||||
|
serverConfig[config_key::defaultContainer] = defaultContainer;
|
||||||
|
|
||||||
|
emit configUpdated(true, serverConfig, serverIndex);
|
||||||
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
reply->deleteLater();
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,29 +4,28 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
|
||||||
#include "configurators/openvpn_configurator.h"
|
#include "configurators/openvpn_configurator.h"
|
||||||
#include "ui/models/containers_model.h"
|
|
||||||
#include "ui/models/servers_model.h"
|
#ifdef Q_OS_IOS
|
||||||
|
#include "platforms/ios/ios_controller.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
class ApiController : public QObject
|
class ApiController : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit ApiController(const QSharedPointer<ServersModel> &serversModel,
|
explicit ApiController(QObject *parent = nullptr);
|
||||||
const QSharedPointer<ContainersModel> &containersModel, QObject *parent = nullptr);
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void updateServerConfigFromApi();
|
void updateServerConfigFromApi(const QString &installationUuid, const int serverIndex, QJsonObject serverConfig);
|
||||||
|
|
||||||
void clearApiConfig();
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void updateStarted();
|
void errorOccurred(ErrorCode errorCode);
|
||||||
void updateFinished(bool isConfigUpdateStarted);
|
void configUpdated(const bool updateConfig, const QJsonObject &config, const int serverIndex);
|
||||||
void errorOccurred(const QString &errorMessage);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ApiPayloadData {
|
struct ApiPayloadData
|
||||||
|
{
|
||||||
OpenVpnConfigurator::ConnectionData certRequest;
|
OpenVpnConfigurator::ConnectionData certRequest;
|
||||||
|
|
||||||
QString wireGuardClientPrivKey;
|
QString wireGuardClientPrivKey;
|
||||||
@@ -36,11 +35,6 @@ private:
|
|||||||
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
ApiPayloadData generateApiPayloadData(const QString &protocol);
|
||||||
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
QJsonObject fillApiPayload(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData);
|
||||||
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
void processApiConfig(const QString &protocol, const ApiController::ApiPayloadData &apiPayloadData, QString &config);
|
||||||
|
|
||||||
QSharedPointer<ServersModel> m_serversModel;
|
|
||||||
QSharedPointer<ContainersModel> m_containersModel;
|
|
||||||
|
|
||||||
bool m_isConfigUpdateStarted = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // APICONTROLLER_H
|
#endif // APICONTROLLER_H
|
||||||
@@ -23,13 +23,13 @@
|
|||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
#include "containers/containers_defs.h"
|
#include "containers/containers_defs.h"
|
||||||
#include "logger.h"
|
#include "core/networkUtilities.h"
|
||||||
#include "core/scripts_registry.h"
|
#include "core/scripts_registry.h"
|
||||||
#include "core/server_defs.h"
|
#include "core/server_defs.h"
|
||||||
|
#include "logger.h"
|
||||||
#include "settings.h"
|
#include "settings.h"
|
||||||
#include "utilities.h"
|
#include "utilities.h"
|
||||||
|
#include "vpnConfigurationController.h"
|
||||||
#include <configurators/vpn_configurator.h>
|
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
@@ -95,10 +95,9 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode
|
ErrorCode ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||||
ServerController::runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr)
|
|
||||||
{
|
{
|
||||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||||
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
Logger::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
||||||
@@ -116,9 +115,8 @@ ServerController::runContainerScript(const ServerCredentials &credentials, Docke
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials,
|
ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
|
||||||
const QString &file, const QString &path,
|
const QString &path, libssh::ScpOverwriteMode overwriteMode)
|
||||||
libssh::ScpOverwriteMode overwriteMode)
|
|
||||||
{
|
{
|
||||||
ErrorCode e = ErrorCode::NoError;
|
ErrorCode e = ErrorCode::NoError;
|
||||||
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
QString tmpFileName = QString("/tmp/%1.tmp").arg(Utils::getRandomString(16));
|
||||||
@@ -156,12 +154,10 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
|
|
||||||
e = runScript(
|
e = runScript(credentials,
|
||||||
credentials,
|
replaceVars(QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
||||||
replaceVars(
|
genVarsForScript(credentials, container)),
|
||||||
QString("sudo docker exec -i $CONTAINER_NAME sh -c \"cat %1 >> %2\"").arg(tmpFileName).arg(path),
|
cbReadStd, cbReadStd);
|
||||||
genVarsForScript(credentials, container)),
|
|
||||||
cbReadStd, cbReadStd);
|
|
||||||
|
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
@@ -172,21 +168,17 @@ ErrorCode ServerController::uploadTextFileToContainer(DockerContainer container,
|
|||||||
return ErrorCode::ServerContainerMissingError;
|
return ErrorCode::ServerContainerMissingError;
|
||||||
}
|
}
|
||||||
|
|
||||||
runScript(credentials,
|
runScript(credentials, replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
||||||
replaceVars(QString("sudo shred -u %1").arg(tmpFileName), genVarsForScript(credentials, container)));
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
QByteArray ServerController::getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||||
const QString &path, ErrorCode *errorCode)
|
ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (errorCode)
|
errorCode = ErrorCode::NoError;
|
||||||
*errorCode = ErrorCode::NoError;
|
|
||||||
|
|
||||||
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"")
|
QString script = QString("sudo docker exec -i %1 sh -c \"xxd -p \'%2\'\"").arg(ContainerProps::containerToString(container)).arg(path);
|
||||||
.arg(ContainerProps::containerToString(container))
|
|
||||||
.arg(path);
|
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -194,12 +186,12 @@ QByteArray ServerController::getTextFileFromContainer(DockerContainer container,
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
*errorCode = runScript(credentials, script, cbReadStdOut);
|
errorCode = runScript(credentials, script, cbReadStdOut);
|
||||||
return QByteArray::fromHex(stdOut.toUtf8());
|
return QByteArray::fromHex(stdOut.toUtf8());
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data,
|
ErrorCode ServerController::uploadFileToHost(const ServerCredentials &credentials, const QByteArray &data, const QString &remotePath,
|
||||||
const QString &remotePath, libssh::ScpOverwriteMode overwriteMode)
|
libssh::ScpOverwriteMode overwriteMode)
|
||||||
{
|
{
|
||||||
auto error = m_sshClient.connectToHost(credentials);
|
auto error = m_sshClient.connectToHost(credentials);
|
||||||
if (error != ErrorCode::NoError) {
|
if (error != ErrorCode::NoError) {
|
||||||
@@ -245,12 +237,10 @@ ErrorCode ServerController::removeAllContainers(const ServerCredentials &credent
|
|||||||
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
|
ErrorCode ServerController::removeContainer(const ServerCredentials &credentials, DockerContainer container)
|
||||||
{
|
{
|
||||||
return runScript(credentials,
|
return runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::remove_container),
|
replaceVars(amnezia::scriptData(SharedScriptType::remove_container), genVarsForScript(credentials, container)));
|
||||||
genVarsForScript(credentials, container)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate)
|
||||||
QJsonObject &config, bool isUpdate)
|
|
||||||
{
|
{
|
||||||
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
|
qDebug().noquote() << "ServerController::setupContainer" << ContainerProps::containerToString(container);
|
||||||
ErrorCode e = ErrorCode::NoError;
|
ErrorCode e = ErrorCode::NoError;
|
||||||
@@ -310,12 +300,11 @@ ErrorCode ServerController::setupContainer(const ServerCredentials &credentials,
|
|||||||
return startupContainerWorker(credentials, container, config);
|
return startupContainerWorker(credentials, container, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
|
||||||
const QJsonObject &oldConfig, QJsonObject &newConfig)
|
QJsonObject &newConfig)
|
||||||
{
|
{
|
||||||
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
bool reinstallRequired = isReinstallContainerRequired(container, oldConfig, newConfig);
|
||||||
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is"
|
qDebug() << "ServerController::updateContainer for container" << container << "reinstall required is" << reinstallRequired;
|
||||||
<< reinstallRequired;
|
|
||||||
|
|
||||||
if (reinstallRequired) {
|
if (reinstallRequired) {
|
||||||
return setupContainer(credentials, container, newConfig, true);
|
return setupContainer(credentials, container, newConfig, true);
|
||||||
@@ -328,8 +317,7 @@ ErrorCode ServerController::updateContainer(const ServerCredentials &credentials
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
|
bool ServerController::isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig)
|
||||||
const QJsonObject &newConfig)
|
|
||||||
{
|
{
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||||
|
|
||||||
@@ -360,7 +348,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
|
|
||||||
if (container == DockerContainer::Awg) {
|
if (container == DockerContainer::Awg) {
|
||||||
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
if ((oldProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::awg::defaultPort))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
|| (oldProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount)
|
||||||
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
!= newProtoConfig.value(config_key::junkPacketCount).toString(protocols::awg::defaultJunkPacketCount))
|
||||||
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
|| (oldProtoConfig.value(config_key::junkPacketMinSize).toString(protocols::awg::defaultJunkPacketMinSize)
|
||||||
@@ -382,7 +370,7 @@ bool ServerController::isReinstallContainerRequired(DockerContainer container, c
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (container == DockerContainer::WireGuard){
|
if (container == DockerContainer::WireGuard) {
|
||||||
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
if (oldProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort)
|
||||||
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
!= newProtoConfig.value(config_key::port).toString(protocols::wireguard::defaultPort))
|
||||||
return true;
|
return true;
|
||||||
@@ -408,8 +396,7 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
|||||||
};
|
};
|
||||||
|
|
||||||
ErrorCode error =
|
ErrorCode error =
|
||||||
runScript(credentials,
|
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::install_docker), genVarsForScript(credentials)),
|
|
||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
qDebug().noquote() << "ServerController::installDockerWorker" << stdOut;
|
||||||
@@ -421,54 +408,47 @@ ErrorCode ServerController::installDockerWorker(const ServerCredentials &credent
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
// create folder on host
|
// create folder on host
|
||||||
return runScript(
|
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
|
||||||
credentials,
|
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::prepare_host), genVarsForScript(credentials, container)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::buildContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
ErrorCode e = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),
|
QString dockerFilePath = amnezia::server::getDockerfileFolder(container) + "/Dockerfile";
|
||||||
amnezia::server::getDockerfileFolder(container) + "/Dockerfile");
|
QString scriptString = QString("sudo rm %1").arg(dockerFilePath);
|
||||||
|
ErrorCode errorCode = runScript(credentials, replaceVars(scriptString, genVarsForScript(credentials, container)));
|
||||||
|
if (errorCode)
|
||||||
|
return errorCode;
|
||||||
|
|
||||||
if (e)
|
errorCode = uploadFileToHost(credentials, amnezia::scriptData(ProtocolScriptType::dockerfile, container).toUtf8(),dockerFilePath);
|
||||||
return e;
|
|
||||||
|
if (errorCode)
|
||||||
|
return errorCode;
|
||||||
|
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
|
||||||
// stdOut += data + "\n";
|
|
||||||
// };
|
|
||||||
|
|
||||||
e = runScript(credentials,
|
errorCode = runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::build_container),
|
replaceVars(amnezia::scriptData(SharedScriptType::build_container), genVarsForScript(credentials, container, config)),
|
||||||
genVarsForScript(credentials, container, config)),
|
|
||||||
cbReadStdOut);
|
cbReadStdOut);
|
||||||
if (e)
|
if (errorCode)
|
||||||
return e;
|
return errorCode;
|
||||||
|
|
||||||
return e;
|
return errorCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
stdOut += data + "\n";
|
stdOut += data + "\n";
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
// auto cbReadStdErr = [&](const QString &data, QSharedPointer<QSsh::SshRemoteProcess> proc) {
|
|
||||||
// stdOut += data + "\n";
|
|
||||||
// };
|
|
||||||
|
|
||||||
ErrorCode e = runScript(credentials,
|
ErrorCode e = runScript(credentials,
|
||||||
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
replaceVars(amnezia::scriptData(ProtocolScriptType::run_container, container),
|
||||||
@@ -485,8 +465,7 @@ ErrorCode ServerController::runContainerWorker(const ServerCredentials &credenti
|
|||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config)
|
||||||
QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -503,13 +482,12 @@ ErrorCode ServerController::configureContainerWorker(const ServerCredentials &cr
|
|||||||
genVarsForScript(credentials, container, config)),
|
genVarsForScript(credentials, container, config)),
|
||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
m_configurator->updateContainerConfigAfterInstallation(container, config, stdOut);
|
VpnConfigurationsController::updateContainerConfigAfterInstallation(container, config, stdOut);
|
||||||
|
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::startupContainerWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
|
QString script = amnezia::scriptData(ProtocolScriptType::container_startup, container);
|
||||||
|
|
||||||
@@ -517,8 +495,7 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode e = uploadTextFileToContainer(container, credentials,
|
ErrorCode e = uploadTextFileToContainer(container, credentials, replaceVars(script, genVarsForScript(credentials, container, config)),
|
||||||
replaceVars(script, genVarsForScript(credentials, container, config)),
|
|
||||||
"/opt/amnezia/start.sh");
|
"/opt/amnezia/start.sh");
|
||||||
if (e)
|
if (e)
|
||||||
return e;
|
return e;
|
||||||
@@ -529,14 +506,15 @@ ErrorCode ServerController::startupContainerWorker(const ServerCredentials &cred
|
|||||||
genVarsForScript(credentials, container, config)));
|
genVarsForScript(credentials, container, config)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials,
|
ServerController::Vars ServerController::genVarsForScript(const ServerCredentials &credentials, DockerContainer container,
|
||||||
DockerContainer container, const QJsonObject &config)
|
const QJsonObject &config)
|
||||||
{
|
{
|
||||||
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
|
const QJsonObject &openvpnConfig = config.value(ProtocolProps::protoToString(Proto::OpenVpn)).toObject();
|
||||||
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
|
const QJsonObject &cloakConfig = config.value(ProtocolProps::protoToString(Proto::Cloak)).toObject();
|
||||||
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
|
const QJsonObject &ssConfig = config.value(ProtocolProps::protoToString(Proto::ShadowSocks)).toObject();
|
||||||
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
const QJsonObject &wireguarConfig = config.value(ProtocolProps::protoToString(Proto::WireGuard)).toObject();
|
||||||
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
const QJsonObject &amneziaWireguarConfig = config.value(ProtocolProps::protoToString(Proto::Awg)).toObject();
|
||||||
|
const QJsonObject &xrayConfig = config.value(ProtocolProps::protoToString(Proto::Xray)).toObject();
|
||||||
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
const QJsonObject &sftpConfig = config.value(ProtocolProps::protoToString(Proto::Sftp)).toObject();
|
||||||
|
|
||||||
Vars vars;
|
Vars vars;
|
||||||
@@ -544,24 +522,19 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
|
vars.append({ { "$REMOTE_HOST", credentials.hostName } });
|
||||||
|
|
||||||
// OpenVPN vars
|
// OpenVPN vars
|
||||||
vars.append(
|
vars.append({ { "$OPENVPN_SUBNET_IP",
|
||||||
{ { "$OPENVPN_SUBNET_IP",
|
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
|
||||||
openvpnConfig.value(config_key::subnet_address).toString(protocols::openvpn::defaultSubnetAddress) } });
|
vars.append({ { "$OPENVPN_SUBNET_CIDR", openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
|
||||||
vars.append({ { "$OPENVPN_SUBNET_CIDR",
|
vars.append({ { "$OPENVPN_SUBNET_MASK", openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
|
||||||
openvpnConfig.value(config_key::subnet_cidr).toString(protocols::openvpn::defaultSubnetCidr) } });
|
|
||||||
vars.append({ { "$OPENVPN_SUBNET_MASK",
|
|
||||||
openvpnConfig.value(config_key::subnet_mask).toString(protocols::openvpn::defaultSubnetMask) } });
|
|
||||||
|
|
||||||
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
|
vars.append({ { "$OPENVPN_PORT", openvpnConfig.value(config_key::port).toString(protocols::openvpn::defaultPort) } });
|
||||||
vars.append(
|
vars.append({ { "$OPENVPN_TRANSPORT_PROTO",
|
||||||
{ { "$OPENVPN_TRANSPORT_PROTO",
|
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
|
||||||
openvpnConfig.value(config_key::transport_proto).toString(protocols::openvpn::defaultTransportProto) } });
|
|
||||||
|
|
||||||
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
|
bool isNcpDisabled = openvpnConfig.value(config_key::ncp_disable).toBool(protocols::openvpn::defaultNcpDisable);
|
||||||
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
|
vars.append({ { "$OPENVPN_NCP_DISABLE", isNcpDisabled ? protocols::openvpn::ncpDisableString : "" } });
|
||||||
|
|
||||||
vars.append({ { "$OPENVPN_CIPHER",
|
vars.append({ { "$OPENVPN_CIPHER", openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
|
||||||
openvpnConfig.value(config_key::cipher).toString(protocols::openvpn::defaultCipher) } });
|
|
||||||
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
|
vars.append({ { "$OPENVPN_HASH", openvpnConfig.value(config_key::hash).toString(protocols::openvpn::defaultHash) } });
|
||||||
|
|
||||||
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
|
bool isTlsAuth = openvpnConfig.value(config_key::tls_auth).toBool(protocols::openvpn::defaultTlsAuth);
|
||||||
@@ -572,39 +545,35 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
}
|
}
|
||||||
|
|
||||||
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
|
vars.append({ { "$OPENVPN_ADDITIONAL_CLIENT_CONFIG",
|
||||||
openvpnConfig.value(config_key::additional_client_config)
|
openvpnConfig.value(config_key::additional_client_config).toString(protocols::openvpn::defaultAdditionalClientConfig) } });
|
||||||
.toString(protocols::openvpn::defaultAdditionalClientConfig) } });
|
|
||||||
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
|
vars.append({ { "$OPENVPN_ADDITIONAL_SERVER_CONFIG",
|
||||||
openvpnConfig.value(config_key::additional_server_config)
|
openvpnConfig.value(config_key::additional_server_config).toString(protocols::openvpn::defaultAdditionalServerConfig) } });
|
||||||
.toString(protocols::openvpn::defaultAdditionalServerConfig) } });
|
|
||||||
|
|
||||||
// ShadowSocks vars
|
// ShadowSocks vars
|
||||||
vars.append({ { "$SHADOWSOCKS_SERVER_PORT",
|
vars.append({ { "$SHADOWSOCKS_SERVER_PORT", ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
|
||||||
ssConfig.value(config_key::port).toString(protocols::shadowsocks::defaultPort) } });
|
|
||||||
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
|
vars.append({ { "$SHADOWSOCKS_LOCAL_PORT",
|
||||||
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
|
ssConfig.value(config_key::local_port).toString(protocols::shadowsocks::defaultLocalProxyPort) } });
|
||||||
vars.append({ { "$SHADOWSOCKS_CIPHER",
|
vars.append({ { "$SHADOWSOCKS_CIPHER", ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
|
||||||
ssConfig.value(config_key::cipher).toString(protocols::shadowsocks::defaultCipher) } });
|
|
||||||
|
|
||||||
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
|
vars.append({ { "$CONTAINER_NAME", ContainerProps::containerToString(container) } });
|
||||||
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
|
vars.append({ { "$DOCKERFILE_FOLDER", "/opt/amnezia/" + ContainerProps::containerToString(container) } });
|
||||||
|
|
||||||
// Cloak vars
|
// Cloak vars
|
||||||
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
|
vars.append({ { "$CLOAK_SERVER_PORT", cloakConfig.value(config_key::port).toString(protocols::cloak::defaultPort) } });
|
||||||
vars.append({ { "$FAKE_WEB_SITE_ADDRESS",
|
vars.append({ { "$FAKE_WEB_SITE_ADDRESS", cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
|
||||||
cloakConfig.value(config_key::site).toString(protocols::cloak::defaultRedirSite) } });
|
|
||||||
|
// Xray vars
|
||||||
|
vars.append({ { "$XRAY_SITE_NAME", xrayConfig.value(config_key::site).toString(protocols::xray::defaultSite) } });
|
||||||
|
|
||||||
// Wireguard vars
|
// Wireguard vars
|
||||||
vars.append(
|
vars.append({ { "$WIREGUARD_SUBNET_IP",
|
||||||
{ { "$WIREGUARD_SUBNET_IP",
|
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
||||||
wireguarConfig.value(config_key::subnet_address).toString(protocols::wireguard::defaultSubnetAddress) } });
|
|
||||||
vars.append({ { "$WIREGUARD_SUBNET_CIDR",
|
vars.append({ { "$WIREGUARD_SUBNET_CIDR",
|
||||||
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
|
wireguarConfig.value(config_key::subnet_cidr).toString(protocols::wireguard::defaultSubnetCidr) } });
|
||||||
vars.append({ { "$WIREGUARD_SUBNET_MASK",
|
vars.append({ { "$WIREGUARD_SUBNET_MASK",
|
||||||
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
|
wireguarConfig.value(config_key::subnet_mask).toString(protocols::wireguard::defaultSubnetMask) } });
|
||||||
|
|
||||||
vars.append({ { "$WIREGUARD_SERVER_PORT",
|
vars.append({ { "$WIREGUARD_SERVER_PORT", wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
|
||||||
wireguarConfig.value(config_key::port).toString(protocols::wireguard::defaultPort) } });
|
|
||||||
|
|
||||||
// IPsec vars
|
// IPsec vars
|
||||||
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
|
vars.append({ { "$IPSEC_VPN_L2TP_NET", "192.168.42.0/24" } });
|
||||||
@@ -627,32 +596,24 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
|
vars.append({ { "$SECONDARY_SERVER_DNS", m_settings->secondaryDns() } });
|
||||||
|
|
||||||
// Sftp vars
|
// Sftp vars
|
||||||
vars.append(
|
vars.append({ { "$SFTP_PORT", sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
|
||||||
{ { "$SFTP_PORT",
|
|
||||||
sftpConfig.value(config_key::port).toString(QString::number(ProtocolProps::defaultPort(Proto::Sftp))) } });
|
|
||||||
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
|
vars.append({ { "$SFTP_USER", sftpConfig.value(config_key::userName).toString() } });
|
||||||
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
vars.append({ { "$SFTP_PASSWORD", sftpConfig.value(config_key::password).toString() } });
|
||||||
|
|
||||||
// Amnezia wireguard vars
|
// Amnezia wireguard vars
|
||||||
vars.append({ { "$AWG_SERVER_PORT",
|
vars.append({ { "$AWG_SERVER_PORT", amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
||||||
amneziaWireguarConfig.value(config_key::port).toString(protocols::awg::defaultPort) } });
|
|
||||||
|
|
||||||
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
vars.append({ { "$JUNK_PACKET_COUNT", amneziaWireguarConfig.value(config_key::junkPacketCount).toString() } });
|
||||||
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
|
vars.append({ { "$JUNK_PACKET_MIN_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMinSize).toString() } });
|
||||||
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
|
vars.append({ { "$JUNK_PACKET_MAX_SIZE", amneziaWireguarConfig.value(config_key::junkPacketMaxSize).toString() } });
|
||||||
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
|
vars.append({ { "$INIT_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::initPacketJunkSize).toString() } });
|
||||||
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE",
|
vars.append({ { "$RESPONSE_PACKET_JUNK_SIZE", amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
|
||||||
amneziaWireguarConfig.value(config_key::responsePacketJunkSize).toString() } });
|
vars.append({ { "$INIT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
|
||||||
vars.append({ { "$INIT_PACKET_MAGIC_HEADER",
|
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
|
||||||
amneziaWireguarConfig.value(config_key::initPacketMagicHeader).toString() } });
|
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
||||||
vars.append({ { "$RESPONSE_PACKET_MAGIC_HEADER",
|
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER", amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
||||||
amneziaWireguarConfig.value(config_key::responsePacketMagicHeader).toString() } });
|
|
||||||
vars.append({ { "$UNDERLOAD_PACKET_MAGIC_HEADER",
|
|
||||||
amneziaWireguarConfig.value(config_key::underloadPacketMagicHeader).toString() } });
|
|
||||||
vars.append({ { "$TRANSPORT_PACKET_MAGIC_HEADER",
|
|
||||||
amneziaWireguarConfig.value(config_key::transportPacketMagicHeader).toString() } });
|
|
||||||
|
|
||||||
QString serverIp = Utils::getIPAddress(credentials.hostName);
|
QString serverIp = NetworkUtilities::getIPAddress(credentials.hostName);
|
||||||
if (!serverIp.isEmpty()) {
|
if (!serverIp.isEmpty()) {
|
||||||
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
vars.append({ { "$SERVER_IP_ADDRESS", serverIp } });
|
||||||
} else {
|
} else {
|
||||||
@@ -662,7 +623,7 @@ ServerController::Vars ServerController::genVarsForScript(const ServerCredential
|
|||||||
return vars;
|
return vars;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode)
|
QString ServerController::checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode)
|
||||||
{
|
{
|
||||||
QString stdOut;
|
QString stdOut;
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
||||||
@@ -674,11 +635,7 @@ QString ServerController::checkSshConnection(const ServerCredentials &credential
|
|||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
};
|
};
|
||||||
|
|
||||||
ErrorCode e =
|
errorCode = runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
||||||
runScript(credentials, amnezia::scriptData(SharedScriptType::check_connection), cbReadStdOut, cbReadStdErr);
|
|
||||||
|
|
||||||
if (errorCode)
|
|
||||||
*errorCode = e;
|
|
||||||
|
|
||||||
return stdOut;
|
return stdOut;
|
||||||
}
|
}
|
||||||
@@ -690,9 +647,7 @@ void ServerController::cancelInstallation()
|
|||||||
|
|
||||||
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
ErrorCode ServerController::setupServerFirewall(const ServerCredentials &credentials)
|
||||||
{
|
{
|
||||||
return runScript(
|
return runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
|
||||||
credentials,
|
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::setup_host_firewall), genVarsForScript(credentials)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
||||||
@@ -704,8 +659,7 @@ QString ServerController::replaceVars(const QString &script, const Vars &vars)
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config)
|
||||||
const QJsonObject &config)
|
|
||||||
{
|
{
|
||||||
if (container == DockerContainer::Dns) {
|
if (container == DockerContainer::Dns) {
|
||||||
return ErrorCode::NoError;
|
return ErrorCode::NoError;
|
||||||
@@ -728,26 +682,46 @@ ErrorCode ServerController::isServerPortBusy(const ServerCredentials &credential
|
|||||||
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
|
QStringList fixedPorts = ContainerProps::fixedPortsForContainer(container);
|
||||||
|
|
||||||
QString defaultPort("%1");
|
QString defaultPort("%1");
|
||||||
QString port =
|
QString port = containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
|
||||||
containerConfig.value(config_key::port).toString(defaultPort.arg(ProtocolProps::defaultPort(protocol)));
|
QString defaultTransportProto = ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
|
||||||
QString defaultTransportProto =
|
|
||||||
ProtocolProps::transportProtoToString(ProtocolProps::defaultTransportProto(protocol), protocol);
|
|
||||||
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
QString transportProto = containerConfig.value(config_key::transport_proto).toString(defaultTransportProto);
|
||||||
|
|
||||||
// TODO reimplement with netstat
|
// TODO reimplement with netstat
|
||||||
QString script =
|
QString script = QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
||||||
QString("which lsof &>/dev/null || true && sudo lsof -i -P -n 2>/dev/null | grep -E ':%1 ").arg(port);
|
|
||||||
for (auto &port : fixedPorts) {
|
for (auto &port : fixedPorts) {
|
||||||
script = script.append("|:%1").arg(port);
|
script = script.append("|:%1").arg(port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (transportProto == "tcpandudp") {
|
||||||
|
QString tcpProtoScript = script;
|
||||||
|
QString udpProtoScript = script;
|
||||||
|
tcpProtoScript.append("' | grep -i tcp");
|
||||||
|
udpProtoScript.append("' | grep -i udp");
|
||||||
|
tcpProtoScript.append(" | grep LISTEN");
|
||||||
|
|
||||||
|
ErrorCode errorCode = runScript(credentials, replaceVars(tcpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCode = runScript(credentials, replaceVars(udpProtoScript, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stdOut.isEmpty()) {
|
||||||
|
return ErrorCode::ServerPortAlreadyAllocatedError;
|
||||||
|
}
|
||||||
|
return ErrorCode::NoError;
|
||||||
|
}
|
||||||
|
|
||||||
script = script.append("' | grep -i %1").arg(transportProto);
|
script = script.append("' | grep -i %1").arg(transportProto);
|
||||||
|
|
||||||
if (transportProto == "tcp") {
|
if (transportProto == "tcp") {
|
||||||
script = script.append(" | grep LISTEN");
|
script = script.append(" | grep LISTEN");
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)),
|
ErrorCode errorCode = runScript(credentials, replaceVars(script, genVarsForScript(credentials, container)), cbReadStdOut, cbReadStdErr);
|
||||||
cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
if (errorCode != ErrorCode::NoError) {
|
||||||
return errorCode;
|
return errorCode;
|
||||||
}
|
}
|
||||||
@@ -775,8 +749,7 @@ ErrorCode ServerController::isUserInSudo(const ServerCredentials &credentials, D
|
|||||||
};
|
};
|
||||||
|
|
||||||
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
const QString scriptData = amnezia::scriptData(SharedScriptType::check_user_in_sudo);
|
||||||
ErrorCode error =
|
ErrorCode error = runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
||||||
runScript(credentials, replaceVars(scriptData, genVarsForScript(credentials)), cbReadStdOut, cbReadStdErr);
|
|
||||||
|
|
||||||
if (!stdOut.contains("sudo"))
|
if (!stdOut.contains("sudo"))
|
||||||
return ErrorCode::ServerUserNotInSudo;
|
return ErrorCode::ServerUserNotInSudo;
|
||||||
@@ -806,9 +779,7 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
|||||||
return ErrorCode::ServerCancelInstallation;
|
return ErrorCode::ServerCancelInstallation;
|
||||||
}
|
}
|
||||||
stdOut.clear();
|
stdOut.clear();
|
||||||
runScript(credentials,
|
runScript(credentials, replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy), genVarsForScript(credentials)),
|
||||||
replaceVars(amnezia::scriptData(SharedScriptType::check_server_is_busy),
|
|
||||||
genVarsForScript(credentials)),
|
|
||||||
cbReadStdOut, cbReadStdErr);
|
cbReadStdOut, cbReadStdErr);
|
||||||
|
|
||||||
if (stdOut.contains("Packet manager not found"))
|
if (stdOut.contains("Packet manager not found"))
|
||||||
@@ -839,147 +810,6 @@ ErrorCode ServerController::isServerDpkgBusy(const ServerCredentials &credential
|
|||||||
return future.result();
|
return future.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorCode ServerController::getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
|
||||||
QMap<DockerContainer, QJsonObject> &installedContainers)
|
|
||||||
{
|
|
||||||
QString stdOut;
|
|
||||||
auto cbReadStdOut = [&](const QString &data, libssh::Client &) {
|
|
||||||
stdOut += data + "\n";
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
};
|
|
||||||
auto cbReadStdErr = [&](const QString &data, libssh::Client &) {
|
|
||||||
stdOut += data + "\n";
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
};
|
|
||||||
|
|
||||||
QString script = QString("sudo docker ps --format '{{.Names}} {{.Ports}}'");
|
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto containersInfo = stdOut.split("\n");
|
|
||||||
for (auto &containerInfo : containersInfo) {
|
|
||||||
if (containerInfo.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const static QRegularExpression containerAndPortRegExp("(amnezia[-a-z]*).*?:([0-9]*)->[0-9]*/(udp|tcp).*");
|
|
||||||
QRegularExpressionMatch containerAndPortMatch = containerAndPortRegExp.match(containerInfo);
|
|
||||||
if (containerAndPortMatch.hasMatch()) {
|
|
||||||
QString name = containerAndPortMatch.captured(1);
|
|
||||||
QString port = containerAndPortMatch.captured(2);
|
|
||||||
QString transportProto = containerAndPortMatch.captured(3);
|
|
||||||
DockerContainer container = ContainerProps::containerFromString(name);
|
|
||||||
|
|
||||||
QJsonObject config;
|
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
|
||||||
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
|
|
||||||
QJsonObject containerConfig;
|
|
||||||
if (protocol == mainProto) {
|
|
||||||
containerConfig.insert(config_key::port, port);
|
|
||||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
|
||||||
|
|
||||||
if (protocol == Proto::Awg) {
|
|
||||||
QString serverConfig = getTextFileFromContainer(container, credentials, protocols::awg::serverConfigPath, &errorCode);
|
|
||||||
|
|
||||||
QMap<QString, QString> serverConfigMap;
|
|
||||||
auto serverConfigLines = serverConfig.split("\n");
|
|
||||||
for (auto &line : serverConfigLines) {
|
|
||||||
auto trimmedLine = line.trimmed();
|
|
||||||
if (trimmedLine.startsWith("[") && trimmedLine.endsWith("]")) {
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
QStringList parts = trimmedLine.split(" = ");
|
|
||||||
if (parts.count() == 2) {
|
|
||||||
serverConfigMap.insert(parts[0].trimmed(), parts[1].trimmed());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
containerConfig[config_key::junkPacketCount] = serverConfigMap.value(config_key::junkPacketCount);
|
|
||||||
containerConfig[config_key::junkPacketMinSize] = serverConfigMap.value(config_key::junkPacketMinSize);
|
|
||||||
containerConfig[config_key::junkPacketMaxSize] = serverConfigMap.value(config_key::junkPacketMaxSize);
|
|
||||||
containerConfig[config_key::initPacketJunkSize] = serverConfigMap.value(config_key::initPacketJunkSize);
|
|
||||||
containerConfig[config_key::responsePacketJunkSize] = serverConfigMap.value(config_key::responsePacketJunkSize);
|
|
||||||
containerConfig[config_key::initPacketMagicHeader] = serverConfigMap.value(config_key::initPacketMagicHeader);
|
|
||||||
containerConfig[config_key::responsePacketMagicHeader] = serverConfigMap.value(config_key::responsePacketMagicHeader);
|
|
||||||
containerConfig[config_key::underloadPacketMagicHeader] = serverConfigMap.value(config_key::underloadPacketMagicHeader);
|
|
||||||
containerConfig[config_key::transportPacketMagicHeader] = serverConfigMap.value(config_key::transportPacketMagicHeader);
|
|
||||||
} else if (protocol == Proto::Sftp) {
|
|
||||||
stdOut.clear();
|
|
||||||
script = QString("sudo docker inspect --format '{{.Config.Cmd}}' %1").arg(name);
|
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto sftpInfo = stdOut.split(":");
|
|
||||||
if (sftpInfo.size() < 2) {
|
|
||||||
logger.error() << "Key parameters for the sftp container are missing";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto userName = sftpInfo.at(0);
|
|
||||||
userName = userName.remove(0, 1);
|
|
||||||
auto password = sftpInfo.at(1);
|
|
||||||
|
|
||||||
containerConfig.insert(config_key::userName, userName);
|
|
||||||
containerConfig.insert(config_key::password, password);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
|
||||||
}
|
|
||||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
|
||||||
}
|
|
||||||
installedContainers.insert(container, config);
|
|
||||||
}
|
|
||||||
const static QRegularExpression torOrDnsRegExp("(amnezia-(?:torwebsite|dns)).*?([0-9]*)/(udp|tcp).*");
|
|
||||||
QRegularExpressionMatch torOrDnsRegMatch = torOrDnsRegExp.match(containerInfo);
|
|
||||||
if (torOrDnsRegMatch.hasMatch()) {
|
|
||||||
QString name = torOrDnsRegMatch.captured(1);
|
|
||||||
QString port = torOrDnsRegMatch.captured(2);
|
|
||||||
QString transportProto = torOrDnsRegMatch.captured(3);
|
|
||||||
DockerContainer container = ContainerProps::containerFromString(name);
|
|
||||||
|
|
||||||
QJsonObject config;
|
|
||||||
Proto mainProto = ContainerProps::defaultProtocol(container);
|
|
||||||
for (auto protocol : ContainerProps::protocolsForContainer(container)) {
|
|
||||||
QJsonObject containerConfig;
|
|
||||||
if (protocol == mainProto) {
|
|
||||||
containerConfig.insert(config_key::port, port);
|
|
||||||
containerConfig.insert(config_key::transport_proto, transportProto);
|
|
||||||
|
|
||||||
if (protocol == Proto::TorWebSite) {
|
|
||||||
stdOut.clear();
|
|
||||||
script = QString("sudo docker exec -i %1 sh -c 'cat /var/lib/tor/hidden_service/hostname'").arg(name);
|
|
||||||
|
|
||||||
ErrorCode errorCode = runScript(credentials, script, cbReadStdOut, cbReadStdErr);
|
|
||||||
if (errorCode != ErrorCode::NoError) {
|
|
||||||
return errorCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stdOut.isEmpty()) {
|
|
||||||
logger.error() << "Key parameters for the tor container are missing";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString onion = stdOut;
|
|
||||||
onion.replace("\n", "");
|
|
||||||
containerConfig.insert(config_key::site, onion);
|
|
||||||
}
|
|
||||||
|
|
||||||
config.insert(config_key::container, ContainerProps::containerToString(container));
|
|
||||||
}
|
|
||||||
config.insert(ProtocolProps::protoToString(protocol), containerConfig);
|
|
||||||
}
|
|
||||||
installedContainers.insert(container, config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ErrorCode::NoError;
|
|
||||||
}
|
|
||||||
|
|
||||||
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
ErrorCode ServerController::getDecryptedPrivateKey(const ServerCredentials &credentials, QString &decryptedPrivateKey,
|
||||||
const std::function<QString()> &callback)
|
const std::function<QString()> &callback)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,22 +25,18 @@ public:
|
|||||||
ErrorCode rebootServer(const ServerCredentials &credentials);
|
ErrorCode rebootServer(const ServerCredentials &credentials);
|
||||||
ErrorCode removeAllContainers(const ServerCredentials &credentials);
|
ErrorCode removeAllContainers(const ServerCredentials &credentials);
|
||||||
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode removeContainer(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config,
|
ErrorCode setupContainer(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config, bool isUpdate = false);
|
||||||
bool isUpdate = false);
|
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &oldConfig,
|
||||||
ErrorCode updateContainer(const ServerCredentials &credentials, DockerContainer container,
|
QJsonObject &newConfig);
|
||||||
const QJsonObject &oldConfig, QJsonObject &newConfig);
|
|
||||||
|
|
||||||
ErrorCode getAlreadyInstalledContainers(const ServerCredentials &credentials,
|
|
||||||
QMap<DockerContainer, QJsonObject> &installedContainers);
|
|
||||||
|
|
||||||
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode startupContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &config = QJsonObject());
|
const QJsonObject &config = QJsonObject());
|
||||||
|
|
||||||
ErrorCode uploadTextFileToContainer(
|
ErrorCode uploadTextFileToContainer(DockerContainer container, const ServerCredentials &credentials, const QString &file,
|
||||||
DockerContainer container, const ServerCredentials &credentials, const QString &file, const QString &path,
|
const QString &path,
|
||||||
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
libssh::ScpOverwriteMode overwriteMode = libssh::ScpOverwriteMode::ScpOverwriteExisting);
|
||||||
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials,
|
QByteArray getTextFileFromContainer(DockerContainer container, const ServerCredentials &credentials, const QString &path,
|
||||||
const QString &path, ErrorCode *errorCode = nullptr);
|
ErrorCode &errorCode);
|
||||||
|
|
||||||
QString replaceVars(const QString &script, const Vars &vars);
|
QString replaceVars(const QString &script, const Vars &vars);
|
||||||
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
|
Vars genVarsForScript(const ServerCredentials &credentials, DockerContainer container = DockerContainer::None,
|
||||||
@@ -50,12 +46,11 @@ public:
|
|||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||||
|
|
||||||
ErrorCode
|
ErrorCode runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
||||||
runContainerScript(const ServerCredentials &credentials, DockerContainer container, QString script,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdOut = nullptr,
|
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
||||||
const std::function<ErrorCode(const QString &, libssh::Client &)> &cbReadStdErr = nullptr);
|
|
||||||
|
|
||||||
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode *errorCode = nullptr);
|
QString checkSshConnection(const ServerCredentials &credentials, ErrorCode &errorCode);
|
||||||
|
|
||||||
void cancelInstallation();
|
void cancelInstallation();
|
||||||
|
|
||||||
@@ -64,18 +59,14 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode installDockerWorker(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode prepareHostWorker(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config = QJsonObject());
|
||||||
const QJsonObject &config = QJsonObject());
|
|
||||||
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode buildContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
||||||
const QJsonObject &config = QJsonObject());
|
const QJsonObject &config = QJsonObject());
|
||||||
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
ErrorCode runContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||||
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode configureContainerWorker(const ServerCredentials &credentials, DockerContainer container, QJsonObject &config);
|
||||||
QJsonObject &config);
|
|
||||||
|
|
||||||
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container,
|
ErrorCode isServerPortBusy(const ServerCredentials &credentials, DockerContainer container, const QJsonObject &config);
|
||||||
const QJsonObject &config);
|
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig, const QJsonObject &newConfig);
|
||||||
bool isReinstallContainerRequired(DockerContainer container, const QJsonObject &oldConfig,
|
|
||||||
const QJsonObject &newConfig);
|
|
||||||
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode isUserInSudo(const ServerCredentials &credentials, DockerContainer container);
|
||||||
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
ErrorCode isServerDpkgBusy(const ServerCredentials &credentials, DockerContainer container);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
#include "vpnConfigurationController.h"
|
||||||
|
|
||||||
|
#include "configurators/awg_configurator.h"
|
||||||
|
#include "configurators/cloak_configurator.h"
|
||||||
|
#include "configurators/ikev2_configurator.h"
|
||||||
|
#include "configurators/openvpn_configurator.h"
|
||||||
|
#include "configurators/shadowsocks_configurator.h"
|
||||||
|
#include "configurators/wireguard_configurator.h"
|
||||||
|
#include "configurators/xray_configurator.h"
|
||||||
|
|
||||||
|
VpnConfigurationsController::VpnConfigurationsController(const std::shared_ptr<Settings> &settings,
|
||||||
|
QSharedPointer<ServerController> serverController, QObject *parent)
|
||||||
|
: QObject { parent }, m_settings(settings), m_serverController(serverController)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QScopedPointer<ConfiguratorBase> VpnConfigurationsController::createConfigurator(const Proto protocol)
|
||||||
|
{
|
||||||
|
switch (protocol) {
|
||||||
|
case Proto::OpenVpn: return QScopedPointer<ConfiguratorBase>(new OpenVpnConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::ShadowSocks: return QScopedPointer<ConfiguratorBase>(new ShadowSocksConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::Cloak: return QScopedPointer<ConfiguratorBase>(new CloakConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::WireGuard: return QScopedPointer<ConfiguratorBase>(new WireguardConfigurator(m_settings, m_serverController, false));
|
||||||
|
case Proto::Awg: return QScopedPointer<ConfiguratorBase>(new AwgConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::Ikev2: return QScopedPointer<ConfiguratorBase>(new Ikev2Configurator(m_settings, m_serverController));
|
||||||
|
case Proto::Xray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
|
||||||
|
case Proto::SSXray: return QScopedPointer<ConfiguratorBase>(new XrayConfigurator(m_settings, m_serverController));
|
||||||
|
default: return QScopedPointer<ConfiguratorBase>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode VpnConfigurationsController::createProtocolConfigForContainer(const ServerCredentials &credentials,
|
||||||
|
const DockerContainer container, QJsonObject &containerConfig)
|
||||||
|
{
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Proto protocol : ContainerProps::protocolsForContainer(container)) {
|
||||||
|
QJsonObject protocolConfig = containerConfig.value(ProtocolProps::protoToString(protocol)).toObject();
|
||||||
|
|
||||||
|
auto configurator = createConfigurator(protocol);
|
||||||
|
QString protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
protocolConfig.insert(config_key::last_config, protocolConfigString);
|
||||||
|
containerConfig.insert(ProtocolProps::protoToString(protocol), protocolConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorCode VpnConfigurationsController::createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns,
|
||||||
|
const ServerCredentials &credentials, const DockerContainer container,
|
||||||
|
const QJsonObject &containerConfig, const Proto protocol,
|
||||||
|
QString &protocolConfigString)
|
||||||
|
{
|
||||||
|
ErrorCode errorCode = ErrorCode::NoError;
|
||||||
|
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto configurator = createConfigurator(protocol);
|
||||||
|
|
||||||
|
protocolConfigString = configurator->createConfig(credentials, container, containerConfig, errorCode);
|
||||||
|
if (errorCode != ErrorCode::NoError) {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
protocolConfigString = configurator->processConfigWithExportSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject VpnConfigurationsController::createVpnConfiguration(const QPair<QString, QString> &dns, const QJsonObject &serverConfig,
|
||||||
|
const QJsonObject &containerConfig, const DockerContainer container,
|
||||||
|
ErrorCode &errorCode)
|
||||||
|
{
|
||||||
|
QJsonObject vpnConfiguration {};
|
||||||
|
|
||||||
|
if (ContainerProps::containerService(container) == ServiceType::Other) {
|
||||||
|
return vpnConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isApiConfig = serverConfig.value(config_key::configVersion).toInt();
|
||||||
|
|
||||||
|
for (ProtocolEnumNS::Proto proto : ContainerProps::protocolsForContainer(container)) {
|
||||||
|
if (isApiConfig && container == DockerContainer::Cloak && proto == ProtocolEnumNS::Proto::ShadowSocks) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString protocolConfigString =
|
||||||
|
containerConfig.value(ProtocolProps::protoToString(proto)).toObject().value(config_key::last_config).toString();
|
||||||
|
|
||||||
|
auto configurator = createConfigurator(proto);
|
||||||
|
protocolConfigString = configurator->processConfigWithLocalSettings(dns, isApiConfig, protocolConfigString);
|
||||||
|
|
||||||
|
QJsonObject vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
|
vpnConfigData = QJsonDocument::fromJson(protocolConfigString.toUtf8()).object();
|
||||||
|
vpnConfiguration.insert(ProtocolProps::key_proto_config_data(proto), vpnConfigData);
|
||||||
|
}
|
||||||
|
|
||||||
|
Proto proto = ContainerProps::defaultProtocol(container);
|
||||||
|
vpnConfiguration[config_key::vpnproto] = ProtocolProps::protoToString(proto);
|
||||||
|
|
||||||
|
vpnConfiguration[config_key::dns1] = dns.first;
|
||||||
|
vpnConfiguration[config_key::dns2] = dns.second;
|
||||||
|
|
||||||
|
vpnConfiguration[config_key::hostName] = serverConfig.value(config_key::hostName).toString();
|
||||||
|
vpnConfiguration[config_key::description] = serverConfig.value(config_key::description).toString();
|
||||||
|
|
||||||
|
vpnConfiguration[config_key::configVersion] = serverConfig.value(config_key::configVersion).toInt();
|
||||||
|
// TODO: try to get hostName, port, description for 3rd party configs
|
||||||
|
// vpnConfiguration[config_key::port] = ...;
|
||||||
|
|
||||||
|
return vpnConfiguration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VpnConfigurationsController::updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig,
|
||||||
|
const QString &stdOut)
|
||||||
|
{
|
||||||
|
Proto mainProto = ContainerProps::defaultProtocol(container);
|
||||||
|
|
||||||
|
if (container == DockerContainer::TorWebSite) {
|
||||||
|
QJsonObject protocol = containerConfig.value(ProtocolProps::protoToString(mainProto)).toObject();
|
||||||
|
|
||||||
|
qDebug() << "amnezia-tor onions" << stdOut;
|
||||||
|
|
||||||
|
QString onion = stdOut;
|
||||||
|
onion.replace("\n", "");
|
||||||
|
protocol.insert(config_key::site, onion);
|
||||||
|
|
||||||
|
containerConfig.insert(ProtocolProps::protoToString(mainProto), protocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef VPNCONFIGIRATIONSCONTROLLER_H
|
||||||
|
#define VPNCONFIGIRATIONSCONTROLLER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
|
||||||
|
#include "configurators/configurator_base.h"
|
||||||
|
#include "containers/containers_defs.h"
|
||||||
|
#include "core/defs.h"
|
||||||
|
#include "settings.h"
|
||||||
|
|
||||||
|
class VpnConfigurationsController : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit VpnConfigurationsController(const std::shared_ptr<Settings> &settings, QSharedPointer<ServerController> serverController, QObject *parent = nullptr);
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
ErrorCode createProtocolConfigForContainer(const ServerCredentials &credentials, const DockerContainer container,
|
||||||
|
QJsonObject &containerConfig);
|
||||||
|
ErrorCode createProtocolConfigString(const bool isApiConfig, const QPair<QString, QString> &dns, const ServerCredentials &credentials,
|
||||||
|
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);
|
||||||
|
|
||||||
|
static void updateContainerConfigAfterInstallation(const DockerContainer container, QJsonObject &containerConfig, const QString &stdOut);
|
||||||
|
signals:
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<ConfiguratorBase> createConfigurator(const Proto protocol);
|
||||||
|
|
||||||
|
std::shared_ptr<Settings> m_settings;
|
||||||
|
QSharedPointer<ServerController> m_serverController;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // VPNCONFIGIRATIONSCONTROLLER_H
|
||||||
+32
-2
@@ -22,12 +22,31 @@ namespace amnezia
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ErrorCode {
|
struct InstalledAppInfo {
|
||||||
|
QString appName;
|
||||||
|
QString packageName;
|
||||||
|
QString appPath;
|
||||||
|
|
||||||
|
bool operator==(const InstalledAppInfo& other) const {
|
||||||
|
if (!packageName.isEmpty()) {
|
||||||
|
return packageName == other.packageName;
|
||||||
|
} else {
|
||||||
|
return appPath == other.appPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace error_code_ns
|
||||||
|
{
|
||||||
|
Q_NAMESPACE
|
||||||
|
// TODO: change to enum class
|
||||||
|
enum ErrorCode {
|
||||||
// General error codes
|
// General error codes
|
||||||
NoError = 0,
|
NoError = 0,
|
||||||
UnknownError = 100,
|
UnknownError = 100,
|
||||||
InternalError = 101,
|
InternalError = 101,
|
||||||
NotImplementedError = 102,
|
NotImplementedError = 102,
|
||||||
|
AmneziaServiceNotRunning = 103,
|
||||||
|
|
||||||
// Server errors
|
// Server errors
|
||||||
ServerCheckFailed = 200,
|
ServerCheckFailed = 200,
|
||||||
@@ -59,6 +78,8 @@ namespace amnezia
|
|||||||
CloakExecutableMissing = 602,
|
CloakExecutableMissing = 602,
|
||||||
AmneziaServiceConnectionFailed = 603,
|
AmneziaServiceConnectionFailed = 603,
|
||||||
ExecutableMissing = 604,
|
ExecutableMissing = 604,
|
||||||
|
XrayExecutableMissing = 605,
|
||||||
|
Tun2SockExecutableMissing = 606,
|
||||||
|
|
||||||
// VPN errors
|
// VPN errors
|
||||||
OpenVpnAdaptersInUseError = 700,
|
OpenVpnAdaptersInUseError = 700,
|
||||||
@@ -70,6 +91,8 @@ namespace amnezia
|
|||||||
OpenSslFailed = 800,
|
OpenSslFailed = 800,
|
||||||
ShadowSocksExecutableCrashed = 801,
|
ShadowSocksExecutableCrashed = 801,
|
||||||
CloakExecutableCrashed = 802,
|
CloakExecutableCrashed = 802,
|
||||||
|
XrayExecutableCrashed = 803,
|
||||||
|
Tun2SockExecutableCrashed = 804,
|
||||||
|
|
||||||
// import and install errors
|
// import and install errors
|
||||||
ImportInvalidConfigError = 900,
|
ImportInvalidConfigError = 900,
|
||||||
@@ -80,6 +103,9 @@ namespace amnezia
|
|||||||
// Api errors
|
// Api errors
|
||||||
ApiConfigDownloadError = 1100,
|
ApiConfigDownloadError = 1100,
|
||||||
ApiConfigAlreadyAdded = 1101,
|
ApiConfigAlreadyAdded = 1101,
|
||||||
|
ApiConfigEmptyError = 1102,
|
||||||
|
ApiConfigTimeoutError = 1103,
|
||||||
|
ApiConfigSslError = 1104,
|
||||||
|
|
||||||
// QFile errors
|
// QFile errors
|
||||||
OpenError = 1200,
|
OpenError = 1200,
|
||||||
@@ -88,7 +114,11 @@ namespace amnezia
|
|||||||
UnspecifiedError = 1203,
|
UnspecifiedError = 1203,
|
||||||
FatalError = 1204,
|
FatalError = 1204,
|
||||||
AbortError = 1205
|
AbortError = 1205
|
||||||
};
|
};
|
||||||
|
Q_ENUM_NS(ErrorCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
using ErrorCode = error_code_ns::ErrorCode;
|
||||||
|
|
||||||
} // namespace amnezia
|
} // namespace amnezia
|
||||||
|
|
||||||
|
|||||||
@@ -8,63 +8,68 @@ QString errorString(ErrorCode code) {
|
|||||||
switch (code) {
|
switch (code) {
|
||||||
|
|
||||||
// General error codes
|
// General error codes
|
||||||
case(NoError): errorMessage = QObject::tr("No error"); break;
|
case(ErrorCode::NoError): errorMessage = QObject::tr("No error"); break;
|
||||||
case(UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
case(ErrorCode::UnknownError): errorMessage = QObject::tr("Unknown Error"); break;
|
||||||
case(NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
case(ErrorCode::NotImplementedError): errorMessage = QObject::tr("Function not implemented"); break;
|
||||||
|
case(ErrorCode::AmneziaServiceNotRunning): errorMessage = QObject::tr("Background service is not running"); break;
|
||||||
|
|
||||||
// Server errors
|
// Server errors
|
||||||
case(ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
case(ErrorCode::ServerCheckFailed): errorMessage = QObject::tr("Server check failed"); break;
|
||||||
case(ServerPortAlreadyAllocatedError): errorMessage = QObject::tr("Server port already used. Check for another software"); break;
|
case(ErrorCode::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(ErrorCode::ServerContainerMissingError): errorMessage = QObject::tr("Server error: Docker container missing"); break;
|
||||||
case(ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
case(ErrorCode::ServerDockerFailedError): errorMessage = QObject::tr("Server error: Docker failed"); break;
|
||||||
case(ServerCancelInstallation): errorMessage = QObject::tr("Installation canceled by user"); break;
|
case(ErrorCode::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(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;
|
||||||
|
|
||||||
// Libssh errors
|
// Libssh errors
|
||||||
case(SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
case(ErrorCode::SshRequestDeniedError): errorMessage = QObject::tr("Ssh request was denied"); break;
|
||||||
case(SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
case(ErrorCode::SshInterruptedError): errorMessage = QObject::tr("Ssh request was interrupted"); break;
|
||||||
case(SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
case(ErrorCode::SshInternalError): errorMessage = QObject::tr("Ssh internal error"); break;
|
||||||
case(SshPrivateKeyError): errorMessage = QObject::tr("Invalid private key or invalid passphrase entered"); break;
|
case(ErrorCode::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(ErrorCode::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;
|
case(ErrorCode::SshTimeoutError): errorMessage = QObject::tr("Timeout connecting to server"); break;
|
||||||
|
|
||||||
// Ssh scp errors
|
// Ssh scp errors
|
||||||
case(SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
case(ErrorCode::SshScpFailureError): errorMessage = QObject::tr("Scp error: Generic failure"); break;
|
||||||
|
|
||||||
// Local errors
|
// Local errors
|
||||||
case (OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
case (ErrorCode::OpenVpnConfigMissing): errorMessage = QObject::tr("OpenVPN config missing"); break;
|
||||||
case (OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
|
case (ErrorCode::OpenVpnManagementServerError): errorMessage = QObject::tr("OpenVPN management server error"); break;
|
||||||
|
|
||||||
// Distro errors
|
// Distro errors
|
||||||
case (OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
case (ErrorCode::OpenVpnExecutableMissing): errorMessage = QObject::tr("OpenVPN executable missing"); break;
|
||||||
case (ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
case (ErrorCode::ShadowSocksExecutableMissing): errorMessage = QObject::tr("ShadowSocks (ss-local) executable missing"); break;
|
||||||
case (CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
case (ErrorCode::CloakExecutableMissing): errorMessage = QObject::tr("Cloak (ck-client) executable missing"); break;
|
||||||
case (AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
case (ErrorCode::AmneziaServiceConnectionFailed): errorMessage = QObject::tr("Amnezia helper service error"); break;
|
||||||
case (OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
case (ErrorCode::OpenSslFailed): errorMessage = QObject::tr("OpenSSL failed"); break;
|
||||||
|
|
||||||
// VPN errors
|
// VPN errors
|
||||||
case (OpenVpnAdaptersInUseError): errorMessage = QObject::tr("Can't connect: another VPN connection is active"); break;
|
case (ErrorCode::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 (ErrorCode::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::AddressPoolError): errorMessage = QObject::tr("VPN pool error: no available addresses"); break;
|
||||||
|
|
||||||
case (ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
case (ErrorCode::ImportInvalidConfigError): errorMessage = QObject::tr("The config does not contain any containers and credentials for connecting to the server"); break;
|
||||||
|
|
||||||
// Android errors
|
// Android errors
|
||||||
case (AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
case (ErrorCode::AndroidError): errorMessage = QObject::tr("VPN connection error"); break;
|
||||||
|
|
||||||
// Api errors
|
// Api errors
|
||||||
case (ApiConfigDownloadError): errorMessage = QObject::tr("Error when retrieving configuration from API"); break;
|
case (ErrorCode::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::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
|
// QFile errors
|
||||||
case(OpenError): errorMessage = QObject::tr("QFile error: The file could not be opened"); break;
|
case(ErrorCode::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(ErrorCode::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(ErrorCode::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(ErrorCode::UnspecifiedError): errorMessage = QObject::tr("QFile error: An unspecified error occurred"); break;
|
||||||
case(FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
case(ErrorCode::FatalError): errorMessage = QObject::tr("QFile error: A fatal error occurred"); break;
|
||||||
case(AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
case(ErrorCode::AbortError): errorMessage = QObject::tr("QFile error: The operation was aborted"); break;
|
||||||
|
|
||||||
case(InternalError):
|
case(ErrorCode::InternalError):
|
||||||
default:
|
default:
|
||||||
errorMessage = QObject::tr("Internal error"); break;
|
errorMessage = QObject::tr("Internal error"); break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
#include "installedAppsImageProvider.h"
|
||||||
|
|
||||||
|
#include "platforms/android/android_controller.h"
|
||||||
|
|
||||||
|
InstalledAppsImageProvider::InstalledAppsImageProvider() : QQuickImageProvider(QQuickImageProvider::Pixmap)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap InstalledAppsImageProvider::requestPixmap(const QString &id, QSize *size, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
return AndroidController::instance()->getAppIcon(id, size, requestedSize);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#ifndef INSTALLEDAPPSIMAGEPROVIDER_H
|
||||||
|
#define INSTALLEDAPPSIMAGEPROVIDER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQuickImageProvider>
|
||||||
|
|
||||||
|
class InstalledAppsImageProvider : public QQuickImageProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
InstalledAppsImageProvider();
|
||||||
|
|
||||||
|
QPixmap requestPixmap(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INSTALLEDAPPSIMAGEPROVIDER_H
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
#include "networkUtilities.h"
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <Ipexport.h>
|
||||||
|
#include <Ws2tcpip.h>
|
||||||
|
#include <ws2ipdef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <Iphlpapi.h>
|
||||||
|
#include <Iptypes.h>
|
||||||
|
#include <WinSock2.h>
|
||||||
|
#include <winsock.h>
|
||||||
|
#include <QNetworkInterface>
|
||||||
|
#include "qendian.h"
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <linux/netlink.h>
|
||||||
|
#include <linux/rtnetlink.h>
|
||||||
|
#include <net/if.h>
|
||||||
|
#include <sys/ioctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||||
|
#include <sys/param.h>
|
||||||
|
#include <sys/sysctl.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <net/route.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <QHostAddress>
|
||||||
|
#include <QHostInfo>
|
||||||
|
|
||||||
|
QRegularExpression NetworkUtilities::ipAddressRegExp()
|
||||||
|
{
|
||||||
|
return QRegularExpression("^((25[0-5]|(2[0-4]|1[0-9]|[1-9]|)[0-9])(\\.(?!$)|$)){4}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegularExpression NetworkUtilities::ipAddressPortRegExp()
|
||||||
|
{
|
||||||
|
return QRegularExpression("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||||
|
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\:[0-9]{1,5}){0,1}$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::ipAddressWithSubnetRegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||||
|
"(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(\\/[0-9]{1,2}){0,1}");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::ipNetwork24RegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}"
|
||||||
|
"0$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::ipPortRegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("^()([1-9]|[1-5]?[0-9]{2,4}|6[1-4][0-9]{3}|65[1-4][0-9]{2}|655[1-2][0-9]|6553[1-5])$");
|
||||||
|
}
|
||||||
|
|
||||||
|
QRegExp NetworkUtilities::domainRegExp()
|
||||||
|
{
|
||||||
|
return QRegExp("(((?!\\-))(xn\\-\\-)?[a-z0-9\\-_]{0,61}[a-z0-9]{1,1}\\.)*(xn\\-\\-)?([a-z0-9\\-]{1,61}|[a-z0-"
|
||||||
|
"9\\-]{1,30})\\.[a-z]{2,}");
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::netMaskFromIpWithSubnet(const QString ip)
|
||||||
|
{
|
||||||
|
if (!ip.contains("/"))
|
||||||
|
return "255.255.255.255";
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int prefix = ip.split("/").at(1).toInt(&ok);
|
||||||
|
if (!ok)
|
||||||
|
return "255.255.255.255";
|
||||||
|
|
||||||
|
unsigned long mask = (0xFFFFFFFF << (32 - prefix)) & 0xFFFFFFFF;
|
||||||
|
|
||||||
|
return QString("%1.%2.%3.%4").arg(mask >> 24).arg((mask >> 16) & 0xFF).arg((mask >> 8) & 0xFF).arg(mask & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::ipAddressFromIpWithSubnet(const QString ip)
|
||||||
|
{
|
||||||
|
if (ip.count(".") != 3)
|
||||||
|
return "";
|
||||||
|
return ip.split("/").first();
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList NetworkUtilities::summarizeRoutes(const QStringList &ips, const QString cidr)
|
||||||
|
{
|
||||||
|
// QMap<int, int>
|
||||||
|
// QHostAddress
|
||||||
|
|
||||||
|
// QMap<QString, QStringList> subnets; // <"a.b", <list subnets>>
|
||||||
|
|
||||||
|
// for (const QString &ip : ips) {
|
||||||
|
// if (ip.count(".") != 3) continue;
|
||||||
|
|
||||||
|
// const QStringList &parts = ip.split(".");
|
||||||
|
// subnets[parts.at(0) + "." + parts.at(1)].append(ip);
|
||||||
|
// }
|
||||||
|
|
||||||
|
return QStringList();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::getIPAddress(const QString &host)
|
||||||
|
{
|
||||||
|
if (ipAddressRegExp().match(host).hasMatch()) {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QHostAddress> addresses = QHostInfo::fromName(host).addresses();
|
||||||
|
if (!addresses.isEmpty()) {
|
||||||
|
return addresses.first().toString();
|
||||||
|
}
|
||||||
|
qDebug() << "Unable to resolve address for " << host;
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString NetworkUtilities::getStringBetween(const QString &s, const QString &a, const QString &b)
|
||||||
|
{
|
||||||
|
int ap = s.indexOf(a), bp = s.indexOf(b, ap + a.length());
|
||||||
|
if (ap < 0 || bp < 0)
|
||||||
|
return QString();
|
||||||
|
ap += a.length();
|
||||||
|
if (bp - ap <= 0)
|
||||||
|
return QString();
|
||||||
|
return s.mid(ap, bp - ap).trimmed();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkUtilities::checkIPv4Format(const QString &ip)
|
||||||
|
{
|
||||||
|
if (ip.isEmpty())
|
||||||
|
return false;
|
||||||
|
int count = ip.count(".");
|
||||||
|
if (count != 3)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
QHostAddress addr(ip);
|
||||||
|
return (addr.protocol() == QAbstractSocket::NetworkLayerProtocol::IPv4Protocol);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NetworkUtilities::checkIpSubnetFormat(const QString &ip)
|
||||||
|
{
|
||||||
|
if (!ip.contains("/"))
|
||||||
|
return checkIPv4Format(ip);
|
||||||
|
|
||||||
|
QStringList parts = ip.split("/");
|
||||||
|
if (parts.size() != 2)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
bool ok;
|
||||||
|
int subnet = parts.at(1).toInt(&ok);
|
||||||
|
if (subnet >= 0 && subnet <= 32 && ok)
|
||||||
|
return checkIPv4Format(parts.at(0));
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// static
|
||||||
|
int NetworkUtilities::AdapterIndexTo(const QHostAddress& dst) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
qDebug() << "Getting Current Internet Adapter that routes to"
|
||||||
|
<< 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);
|
||||||
|
qDebug() << "Internet Adapter:" << adapter.name();
|
||||||
|
return routeInfo.dwForwardIfIndex;
|
||||||
|
#endif
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
DWORD GetAdaptersAddressesWrapper(const ULONG Family,
|
||||||
|
const ULONG Flags,
|
||||||
|
const PVOID Reserved,
|
||||||
|
_Out_ PIP_ADAPTER_ADDRESSES& pAdapterAddresses) {
|
||||||
|
DWORD dwRetVal = 0;
|
||||||
|
int iter = 0;
|
||||||
|
constexpr int max_iter = 3;
|
||||||
|
ULONG AdapterAddressesLen = 15000;
|
||||||
|
do {
|
||||||
|
// xassert2(pAdapterAddresses == nullptr);
|
||||||
|
pAdapterAddresses = (IP_ADAPTER_ADDRESSES*)malloc(AdapterAddressesLen);
|
||||||
|
if (pAdapterAddresses == nullptr) {
|
||||||
|
qDebug() << "can not malloc" << AdapterAddressesLen << "bytes";
|
||||||
|
return ERROR_OUTOFMEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
dwRetVal = GetAdaptersAddresses(Family, Flags, NULL, pAdapterAddresses, &AdapterAddressesLen);
|
||||||
|
|
||||||
|
if (dwRetVal == ERROR_BUFFER_OVERFLOW) {
|
||||||
|
free(pAdapterAddresses);
|
||||||
|
pAdapterAddresses = nullptr;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
iter++;
|
||||||
|
} while ((dwRetVal == ERROR_BUFFER_OVERFLOW) && (iter < max_iter));
|
||||||
|
|
||||||
|
if (dwRetVal != NO_ERROR) {
|
||||||
|
qDebug() << "Family: " << Family << ", Flags: " << Flags << " AdapterAddressesLen: " << AdapterAddressesLen <<
|
||||||
|
", dwRetVal:" << dwRetVal << ", iter: " << iter;
|
||||||
|
if (pAdapterAddresses) {
|
||||||
|
free(pAdapterAddresses);
|
||||||
|
pAdapterAddresses = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return dwRetVal;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString NetworkUtilities::getGatewayAndIface()
|
||||||
|
{
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
constexpr int BUFF_LEN = 100;
|
||||||
|
char buff[BUFF_LEN] = {'\0'};
|
||||||
|
QString result;
|
||||||
|
|
||||||
|
PIP_ADAPTER_ADDRESSES pAdapterAddresses = nullptr;
|
||||||
|
DWORD dwRetVal =
|
||||||
|
GetAdaptersAddressesWrapper(AF_INET, GAA_FLAG_INCLUDE_GATEWAYS, NULL, pAdapterAddresses);
|
||||||
|
|
||||||
|
if (dwRetVal != NO_ERROR) {
|
||||||
|
qDebug() << "ipv4 stack detect GetAdaptersAddresses failed.";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
PIP_ADAPTER_ADDRESSES pCurAddress = pAdapterAddresses;
|
||||||
|
while (pCurAddress) {
|
||||||
|
PIP_ADAPTER_GATEWAY_ADDRESS_LH gateway = pCurAddress->FirstGatewayAddress;
|
||||||
|
if (gateway) {
|
||||||
|
SOCKET_ADDRESS gateway_address = gateway->Address;
|
||||||
|
if (gateway->Address.lpSockaddr->sa_family == AF_INET) {
|
||||||
|
sockaddr_in* sa_in = (sockaddr_in*)gateway->Address.lpSockaddr;
|
||||||
|
QString gw = inet_ntop(AF_INET, &(sa_in->sin_addr), buff, BUFF_LEN);
|
||||||
|
qDebug() << "gateway IPV4:" << gw;
|
||||||
|
struct sockaddr_in addr;
|
||||||
|
if (inet_pton(AF_INET, buff, &addr.sin_addr) == 1) {
|
||||||
|
qDebug() << "this is true v4 !";
|
||||||
|
result = gw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pCurAddress = pCurAddress->Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(pAdapterAddresses);
|
||||||
|
return result;
|
||||||
|
#endif
|
||||||
|
#ifdef Q_OS_LINUX
|
||||||
|
constexpr int BUFFER_SIZE = 100;
|
||||||
|
int received_bytes = 0, msg_len = 0, route_attribute_len = 0;
|
||||||
|
int sock = -1, msgseq = 0;
|
||||||
|
struct nlmsghdr *nlh, *nlmsg;
|
||||||
|
struct rtmsg *route_entry;
|
||||||
|
// This struct contain route attributes (route type)
|
||||||
|
struct rtattr *route_attribute;
|
||||||
|
char gateway_address[INET_ADDRSTRLEN], interface[IF_NAMESIZE];
|
||||||
|
char msgbuf[BUFFER_SIZE], buffer[BUFFER_SIZE];
|
||||||
|
char *ptr = buffer;
|
||||||
|
struct timeval tv;
|
||||||
|
|
||||||
|
if ((sock = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) < 0) {
|
||||||
|
perror("socket failed");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
memset(msgbuf, 0, sizeof(msgbuf));
|
||||||
|
memset(gateway_address, 0, sizeof(gateway_address));
|
||||||
|
memset(interface, 0, sizeof(interface));
|
||||||
|
memset(buffer, 0, sizeof(buffer));
|
||||||
|
|
||||||
|
/* point the header and the msg structure pointers into the buffer */
|
||||||
|
nlmsg = (struct nlmsghdr *)msgbuf;
|
||||||
|
|
||||||
|
/* Fill in the nlmsg header*/
|
||||||
|
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||||
|
nlmsg->nlmsg_type = RTM_GETROUTE; // Get the routes from kernel routing table .
|
||||||
|
nlmsg->nlmsg_flags = NLM_F_DUMP | NLM_F_REQUEST; // The message is a request for dump.
|
||||||
|
nlmsg->nlmsg_seq = msgseq++; // Sequence of the message packet.
|
||||||
|
nlmsg->nlmsg_pid = getpid(); // PID of process sending the request.
|
||||||
|
|
||||||
|
/* 1 Sec Timeout to avoid stall */
|
||||||
|
tv.tv_sec = 1;
|
||||||
|
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (struct timeval *)&tv, sizeof(struct timeval));
|
||||||
|
/* send msg */
|
||||||
|
if (send(sock, nlmsg, nlmsg->nlmsg_len, 0) < 0) {
|
||||||
|
perror("send failed");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* receive response */
|
||||||
|
do
|
||||||
|
{
|
||||||
|
received_bytes = recv(sock, ptr, sizeof(buffer) - msg_len, 0);
|
||||||
|
if (received_bytes < 0) {
|
||||||
|
perror("Error in recv");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
nlh = (struct nlmsghdr *) ptr;
|
||||||
|
|
||||||
|
/* Check if the header is valid */
|
||||||
|
if((NLMSG_OK(nlmsg, received_bytes) == 0) ||
|
||||||
|
(nlmsg->nlmsg_type == NLMSG_ERROR))
|
||||||
|
{
|
||||||
|
perror("Error in received packet");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If we received all data break */
|
||||||
|
if (nlh->nlmsg_type == NLMSG_DONE)
|
||||||
|
break;
|
||||||
|
else {
|
||||||
|
ptr += received_bytes;
|
||||||
|
msg_len += received_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Break if its not a multi part message */
|
||||||
|
if ((nlmsg->nlmsg_flags & NLM_F_MULTI) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
while ((nlmsg->nlmsg_seq != msgseq) || (nlmsg->nlmsg_pid != getpid()));
|
||||||
|
|
||||||
|
/* parse response */
|
||||||
|
for ( ; NLMSG_OK(nlh, received_bytes); nlh = NLMSG_NEXT(nlh, received_bytes))
|
||||||
|
{
|
||||||
|
/* Get the route data */
|
||||||
|
route_entry = (struct rtmsg *) NLMSG_DATA(nlh);
|
||||||
|
|
||||||
|
/* We are just interested in main routing table */
|
||||||
|
if (route_entry->rtm_table != RT_TABLE_MAIN)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
route_attribute = (struct rtattr *) RTM_RTA(route_entry);
|
||||||
|
route_attribute_len = RTM_PAYLOAD(nlh);
|
||||||
|
|
||||||
|
/* Loop through all attributes */
|
||||||
|
for ( ; RTA_OK(route_attribute, route_attribute_len);
|
||||||
|
route_attribute = RTA_NEXT(route_attribute, route_attribute_len))
|
||||||
|
{
|
||||||
|
switch(route_attribute->rta_type) {
|
||||||
|
case RTA_OIF:
|
||||||
|
if_indextoname(*(int *)RTA_DATA(route_attribute), interface);
|
||||||
|
break;
|
||||||
|
case RTA_GATEWAY:
|
||||||
|
inet_ntop(AF_INET, RTA_DATA(route_attribute),
|
||||||
|
gateway_address, sizeof(gateway_address));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((*gateway_address) && (*interface)) {
|
||||||
|
qDebug() << "Gateway " << gateway_address << " for interface " << interface;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(sock);
|
||||||
|
return gateway_address;
|
||||||
|
#endif
|
||||||
|
#if defined(Q_OS_MAC) && !defined(Q_OS_IOS)
|
||||||
|
QString gateway;
|
||||||
|
int mib[] = {CTL_NET, PF_ROUTE, 0, 0, NET_RT_FLAGS, RTF_GATEWAY};
|
||||||
|
int afinet_type[] = {AF_INET, AF_INET6};
|
||||||
|
|
||||||
|
for (int ip_type = 0; ip_type <= 1; ip_type++)
|
||||||
|
{
|
||||||
|
mib[3] = afinet_type[ip_type];
|
||||||
|
|
||||||
|
size_t needed = 0;
|
||||||
|
if (sysctl(mib, sizeof(mib) / sizeof(int), nullptr, &needed, nullptr, 0) < 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
char* buf;
|
||||||
|
if ((buf = new char[needed]) == 0)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
if (sysctl(mib, sizeof(mib) / sizeof(int), buf, &needed, nullptr, 0) < 0)
|
||||||
|
{
|
||||||
|
qDebug() << "sysctl: net.route.0.0.dump";
|
||||||
|
delete[] buf;
|
||||||
|
return gateway;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct rt_msghdr* rt;
|
||||||
|
for (char* p = buf; p < buf + needed; p += rt->rtm_msglen)
|
||||||
|
{
|
||||||
|
rt = reinterpret_cast<struct rt_msghdr*>(p);
|
||||||
|
struct sockaddr* sa = reinterpret_cast<struct sockaddr*>(rt + 1);
|
||||||
|
struct sockaddr* sa_tab[RTAX_MAX];
|
||||||
|
for (int i = 0; i < RTAX_MAX; i++)
|
||||||
|
{
|
||||||
|
if (rt->rtm_addrs & (1 << i))
|
||||||
|
{
|
||||||
|
sa_tab[i] = sa;
|
||||||
|
sa = reinterpret_cast<struct sockaddr*>(
|
||||||
|
reinterpret_cast<char*>(sa) +
|
||||||
|
((sa->sa_len) > 0 ? (1 + (((sa->sa_len) - 1) | (sizeof(long) - 1))) : sizeof(long)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sa_tab[i] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((rt->rtm_addrs & (RTA_DST | RTA_GATEWAY)) == (RTA_DST | RTA_GATEWAY)) &&
|
||||||
|
sa_tab[RTAX_DST]->sa_family == afinet_type[ip_type] &&
|
||||||
|
sa_tab[RTAX_GATEWAY]->sa_family == afinet_type[ip_type])
|
||||||
|
{
|
||||||
|
if (afinet_type[ip_type] == AF_INET)
|
||||||
|
{
|
||||||
|
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||||
|
{
|
||||||
|
char dstStr4[INET_ADDRSTRLEN];
|
||||||
|
char srcStr4[INET_ADDRSTRLEN];
|
||||||
|
memcpy(srcStr4,
|
||||||
|
&(reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_GATEWAY]))->sin_addr,
|
||||||
|
sizeof(struct in_addr));
|
||||||
|
if (inet_ntop(AF_INET, srcStr4, dstStr4, INET_ADDRSTRLEN) != nullptr)
|
||||||
|
gateway = dstStr4;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (afinet_type[ip_type] == AF_INET6)
|
||||||
|
{
|
||||||
|
if ((reinterpret_cast<struct sockaddr_in*>(sa_tab[RTAX_DST]))->sin_addr.s_addr == 0)
|
||||||
|
{
|
||||||
|
char dstStr6[INET6_ADDRSTRLEN];
|
||||||
|
char srcStr6[INET6_ADDRSTRLEN];
|
||||||
|
memcpy(srcStr6,
|
||||||
|
&(reinterpret_cast<struct sockaddr_in6*>(sa_tab[RTAX_GATEWAY]))->sin6_addr,
|
||||||
|
sizeof(struct in6_addr));
|
||||||
|
if (inet_ntop(AF_INET6, srcStr6, dstStr6, INET6_ADDRSTRLEN) != nullptr)
|
||||||
|
gateway = dstStr6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
return gateway;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
#ifndef NETWORKUTILITIES_H
|
||||||
|
#define NETWORKUTILITIES_H
|
||||||
|
|
||||||
|
#include <QRegularExpression>
|
||||||
|
#include <QRegExp>
|
||||||
|
#include <QString>
|
||||||
|
#include <QHostAddress>
|
||||||
|
|
||||||
|
|
||||||
|
class NetworkUtilities : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
static QString getIPAddress(const QString &host);
|
||||||
|
static QString getStringBetween(const QString &s, const QString &a, const QString &b);
|
||||||
|
static bool checkIPv4Format(const QString &ip);
|
||||||
|
static bool checkIpSubnetFormat(const QString &ip);
|
||||||
|
static QString getGatewayAndIface();
|
||||||
|
// Returns the Interface Index that could Route to dst
|
||||||
|
static int AdapterIndexTo(const QHostAddress& dst);
|
||||||
|
|
||||||
|
static QRegularExpression ipAddressRegExp();
|
||||||
|
static QRegularExpression ipAddressPortRegExp();
|
||||||
|
static QRegExp ipAddressWithSubnetRegExp();
|
||||||
|
static QRegExp ipNetwork24RegExp();
|
||||||
|
static QRegExp ipPortRegExp();
|
||||||
|
static QRegExp domainRegExp();
|
||||||
|
|
||||||
|
static QString netMaskFromIpWithSubnet(const QString ip);
|
||||||
|
static QString ipAddressFromIpWithSubnet(const QString ip);
|
||||||
|
|
||||||
|
static QStringList summarizeRoutes(const QStringList &ips, const QString cidr);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // NETWORKUTILITIES_H
|
||||||
@@ -13,11 +13,12 @@ QString amnezia::scriptFolder(amnezia::DockerContainer container)
|
|||||||
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
case DockerContainer::WireGuard: return QLatin1String("wireguard");
|
||||||
case DockerContainer::Awg: return QLatin1String("awg");
|
case DockerContainer::Awg: return QLatin1String("awg");
|
||||||
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
case DockerContainer::Ipsec: return QLatin1String("ipsec");
|
||||||
|
case DockerContainer::Xray: return QLatin1String("xray");
|
||||||
|
|
||||||
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
case DockerContainer::TorWebSite: return QLatin1String("website_tor");
|
||||||
case DockerContainer::Dns: return QLatin1String("dns");
|
case DockerContainer::Dns: return QLatin1String("dns");
|
||||||
case DockerContainer::Sftp: return QLatin1String("sftp");
|
case DockerContainer::Sftp: return QLatin1String("sftp");
|
||||||
default: return "";
|
default: return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,6 +48,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
|
|||||||
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
case ProtocolScriptType::openvpn_template: return QLatin1String("template.ovpn");
|
||||||
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
|
||||||
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
|
||||||
|
case ProtocolScriptType::xray_template: return QLatin1String("template.json");
|
||||||
default: return QString();
|
default: return QString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ enum ProtocolScriptType {
|
|||||||
container_startup,
|
container_startup,
|
||||||
openvpn_template,
|
openvpn_template,
|
||||||
wireguard_template,
|
wireguard_template,
|
||||||
awg_template
|
awg_template,
|
||||||
|
xray_template
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::inbounds
|
||||||
|
{
|
||||||
|
|
||||||
|
//"inbounds": [
|
||||||
|
// {
|
||||||
|
// "listen": "127.0.0.1",
|
||||||
|
// "port": 10808,
|
||||||
|
// "protocol": "socks",
|
||||||
|
// "settings": {
|
||||||
|
// "udp": true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//],
|
||||||
|
|
||||||
|
const static QString listen = "127.0.0.1";
|
||||||
|
const static int port = 10808;
|
||||||
|
const static QString protocol = "socks";
|
||||||
|
|
||||||
|
QJsonObject GenerateInboundEntry()
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonIO::SetValue(root, listen, "listen");
|
||||||
|
QJsonIO::SetValue(root, port, "port");
|
||||||
|
QJsonIO::SetValue(root, protocol, "protocol");
|
||||||
|
QJsonIO::SetValue(root, true, "settings", "udp");
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::inbounds
|
||||||
|
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QList>
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "transfer.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::outbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(domainStrategy, redirect)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateBlackHoleOUT(bool useHTTP)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonObject resp;
|
||||||
|
resp.insert("type", useHTTP ? "http" : "none");
|
||||||
|
root.insert("response", resp);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(address, port, method, password)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &_servers)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray x;
|
||||||
|
|
||||||
|
for (const auto &server : _servers)
|
||||||
|
{
|
||||||
|
x.append(GenerateShadowSocksServerOUT(server.address, server.port, server.method, server.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.insert("servers", x);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateHTTPSOCKSOut(const QString &addr, int port, bool useAuth, const QString &username, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonIO::SetValue(root, addr, "servers", 0, "address");
|
||||||
|
QJsonIO::SetValue(root, port, "servers", 0, "port");
|
||||||
|
if (useAuth)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(root, username, "servers", 0, "users", 0, "user");
|
||||||
|
QJsonIO::SetValue(root, password, "servers", 0, "users", 0, "pass");
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateOutboundEntry(const QString &tag, const QString &protocol, const QJsonObject &settings, const QJsonObject &streamSettings,
|
||||||
|
const QJsonObject &mux, const QString &sendThrough)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(sendThrough, protocol, settings, tag, streamSettings, mux)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &_servers)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray x;
|
||||||
|
|
||||||
|
for (const auto &server : _servers)
|
||||||
|
{
|
||||||
|
x.append(GenerateTrojanServerOUT(server.address, server.port, server.password));
|
||||||
|
}
|
||||||
|
|
||||||
|
root.insert("servers", x);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password)
|
||||||
|
{
|
||||||
|
QJsonObject root;
|
||||||
|
JADD(address, port, password)
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::outbounds
|
||||||
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#ifndef SERIALIZATION_H
|
||||||
|
#define SERIALIZATION_H
|
||||||
|
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include "transfer.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization
|
||||||
|
{
|
||||||
|
namespace vmess
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||||
|
} // namespace vmess
|
||||||
|
|
||||||
|
namespace vmess_new
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vmess, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const StreamSettingsObject &transfer, const VMessServerObject &server, const QString &alias);
|
||||||
|
} // namespace vmess_new
|
||||||
|
|
||||||
|
namespace vless
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &vless, QString *alias, QString *errMessage);
|
||||||
|
} // namespace vless
|
||||||
|
|
||||||
|
namespace ss
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &ss, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool isSip002);
|
||||||
|
} // namespace ss
|
||||||
|
|
||||||
|
namespace ssd
|
||||||
|
{
|
||||||
|
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList);
|
||||||
|
} // namespace ssd
|
||||||
|
|
||||||
|
namespace trojan
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &trojan, QString *alias, QString *errMessage);
|
||||||
|
const QString Serialize(const TrojanObject &server, const QString &alias);
|
||||||
|
} // namespace trojan
|
||||||
|
|
||||||
|
namespace outbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateFreedomOUT(const QString &domainStrategy, const QString &redirect);
|
||||||
|
QJsonObject GenerateBlackHoleOUT(bool useHTTP);
|
||||||
|
QJsonObject GenerateShadowSocksOUT(const QList<ShadowSocksServerObject> &servers);
|
||||||
|
QJsonObject GenerateShadowSocksServerOUT(const QString &address, int port, const QString &method, const QString &password);
|
||||||
|
QJsonObject GenerateHTTPSOCKSOut(const QString &address, int port, bool useAuth, const QString &username, const QString &password);
|
||||||
|
QJsonObject GenerateTrojanOUT(const QList<TrojanObject> &servers);
|
||||||
|
QJsonObject GenerateTrojanServerOUT(const QString &address, int port, const QString &password);
|
||||||
|
QJsonObject GenerateOutboundEntry(const QString &tag, //
|
||||||
|
const QString &protocol, //
|
||||||
|
const QJsonObject &settings, //
|
||||||
|
const QJsonObject &streamSettings, //
|
||||||
|
const QJsonObject &mux = {}, //
|
||||||
|
const QString &sendThrough = "0.0.0.0");
|
||||||
|
} // namespace outbounds
|
||||||
|
|
||||||
|
namespace inbounds
|
||||||
|
{
|
||||||
|
QJsonObject GenerateInboundEntry();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // SERIALIZATION_H
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||||
|
|
||||||
|
namespace amnezia::serialization::ss
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &ssUri, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
ShadowSocksServerObject server;
|
||||||
|
QString d_name;
|
||||||
|
|
||||||
|
// auto ssUri = _ssUri.toStdString();
|
||||||
|
if (ssUri.length() < 5)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("SS URI is too short");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto uri = ssUri.mid(5);
|
||||||
|
auto hashPos = uri.lastIndexOf("#");
|
||||||
|
|
||||||
|
if (hashPos >= 0)
|
||||||
|
{
|
||||||
|
// Get the name/remark
|
||||||
|
d_name = uri.mid(uri.lastIndexOf("#") + 1);
|
||||||
|
uri.truncate(hashPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto atPos = uri.indexOf('@');
|
||||||
|
|
||||||
|
if (atPos < 0)
|
||||||
|
{
|
||||||
|
// Old URI scheme
|
||||||
|
QString decoded = QByteArray::fromBase64(uri.toUtf8(), QByteArray::Base64Option::OmitTrailingEquals);
|
||||||
|
auto colonPos = decoded.indexOf(':');
|
||||||
|
|
||||||
|
if (colonPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.method = decoded.left(colonPos);
|
||||||
|
decoded.remove(0, colonPos + 1);
|
||||||
|
atPos = decoded.lastIndexOf('@');
|
||||||
|
|
||||||
|
if (atPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the at separator between password and hostname");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.password = decoded.mid(0, atPos);
|
||||||
|
decoded.remove(0, atPos + 1);
|
||||||
|
colonPos = decoded.lastIndexOf(':');
|
||||||
|
|
||||||
|
if (colonPos < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between hostname and port");
|
||||||
|
}
|
||||||
|
|
||||||
|
server.address = decoded.mid(0, colonPos);
|
||||||
|
server.port = decoded.mid(colonPos + 1).toInt();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// SIP002 URI scheme
|
||||||
|
auto x = QUrl::fromUserInput(uri);
|
||||||
|
server.address = x.host();
|
||||||
|
server.port = x.port();
|
||||||
|
const auto userInfo = Utils::SafeBase64Decode(x.userName());
|
||||||
|
const auto userInfoSp = userInfo.indexOf(':');
|
||||||
|
|
||||||
|
if (userInfoSp < 0)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("Can't find the colon separator between method and password");
|
||||||
|
return QJsonObject{};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto method = userInfo.mid(0, userInfoSp);
|
||||||
|
server.method = method;
|
||||||
|
server.password = userInfo.mid(userInfoSp + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
d_name = QUrl::fromPercentEncoding(d_name.toUtf8());
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ server }), {}));
|
||||||
|
JADD(outbounds)
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
*alias = alias->isEmpty() ? d_name : *alias + "_" + d_name;
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString Serialize(const ShadowSocksServerObject &server, const QString &alias, bool)
|
||||||
|
{
|
||||||
|
QUrl url;
|
||||||
|
const auto plainUserInfo = server.method + ":" + server.password;
|
||||||
|
const auto userinfo = plainUserInfo.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals);
|
||||||
|
url.setUserInfo(userinfo);
|
||||||
|
url.setScheme("ss");
|
||||||
|
url.setHost(server.address);
|
||||||
|
url.setPort(server.port);
|
||||||
|
url.setFragment(alias);
|
||||||
|
return url.toString(QUrl::ComponentFormattingOption::FullyEncoded);
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::ss
|
||||||
|
|
||||||
@@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Naive SSD Decoder for Qv2ray
|
||||||
|
*
|
||||||
|
* @author DuckSoft <realducksoft@gmail.com>
|
||||||
|
* @copyright Licensed under GPLv3.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
#include "utilities.h"
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
const inline QString QV2RAY_SSD_DEFAULT_NAME_PATTERN = "%1 - %2 (rate %3)";
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::ssd
|
||||||
|
{
|
||||||
|
// These below are super strict checking schemes, but necessary.
|
||||||
|
#define MUST_EXIST(fieldName) \
|
||||||
|
if (!obj.contains((fieldName)) || obj[(fieldName)].isUndefined() || obj[(fieldName)].isNull()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must exist").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_PORT(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (int value = obj[(fieldName)].toInt(-1); value < 0 || value > 65535) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be valid port number"); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_STRING(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (!obj[(fieldName)].isString()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be of type 'string'").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
#define MUST_ARRAY(fieldName) \
|
||||||
|
MUST_EXIST(fieldName); \
|
||||||
|
if (!obj[(fieldName)].isArray()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json: field %1 must be an array").arg(fieldName); \
|
||||||
|
return {}; \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define SERVER_SHOULD_BE_OBJECT(server) \
|
||||||
|
if (!server.isObject()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: server must be an object"); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
#define SHOULD_EXIST(fieldName) \
|
||||||
|
if (serverObject[(fieldName)].isUndefined()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: missing required field %1").arg(fieldName); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
#define SHOULD_STRING(fieldName) \
|
||||||
|
SHOULD_EXIST(fieldName); \
|
||||||
|
if (!serverObject[(fieldName)].isString()) \
|
||||||
|
{ \
|
||||||
|
*logList << QObject::tr("Skipping invalid ssd server: field %1 should be of type 'string'").arg(fieldName); \
|
||||||
|
continue; \
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<std::pair<QString, QJsonObject>> Deserialize(const QString &uri, QString *groupName, QStringList *logList)
|
||||||
|
{
|
||||||
|
// ssd links should begin with "ssd://"
|
||||||
|
if (!uri.startsWith("ssd://"))
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: should begin with ssd://");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode base64
|
||||||
|
const auto ssdURIBody = uri.mid(6, uri.length() - 6); //(&uri, 6, uri.length() - 6);
|
||||||
|
const auto decodedJSON = Utils::SafeBase64Decode(ssdURIBody).toUtf8();
|
||||||
|
|
||||||
|
if (decodedJSON.length() == 0)
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: base64 parse failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto decodeError = Utils::VerifyJsonString(decodedJSON);
|
||||||
|
if (!decodeError.isEmpty())
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: json parse failed");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// casting to object
|
||||||
|
const auto obj = Utils::JsonFromString(decodedJSON);
|
||||||
|
|
||||||
|
// obj.airport
|
||||||
|
MUST_STRING("airport");
|
||||||
|
*groupName = obj["airport"].toString();
|
||||||
|
|
||||||
|
// obj.port
|
||||||
|
MUST_PORT("port");
|
||||||
|
const int port = obj["port"].toInt();
|
||||||
|
|
||||||
|
// obj.encryption
|
||||||
|
MUST_STRING("encryption");
|
||||||
|
const auto encryption = obj["encryption"].toString();
|
||||||
|
|
||||||
|
// check: rc4-md5 is not supported by v2ray-core
|
||||||
|
// TODO: more checks, including all algorithms
|
||||||
|
if (encryption.toLower() == "rc4-md5")
|
||||||
|
{
|
||||||
|
*logList << QObject::tr("Invalid ssd link: rc4-md5 encryption is not supported by v2ray-core");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// obj.password
|
||||||
|
MUST_STRING("password");
|
||||||
|
const auto password = obj["password"].toString();
|
||||||
|
// obj.servers
|
||||||
|
MUST_ARRAY("servers");
|
||||||
|
//
|
||||||
|
QList<std::pair<QString, QJsonObject>> serverList;
|
||||||
|
//
|
||||||
|
|
||||||
|
// iterate through the servers
|
||||||
|
for (const auto &server : obj["servers"].toArray())
|
||||||
|
{
|
||||||
|
SERVER_SHOULD_BE_OBJECT(server);
|
||||||
|
const auto serverObject = server.toObject();
|
||||||
|
ShadowSocksServerObject ssObject;
|
||||||
|
|
||||||
|
// encryption
|
||||||
|
ssObject.method = encryption;
|
||||||
|
|
||||||
|
// password
|
||||||
|
ssObject.password = password;
|
||||||
|
|
||||||
|
// address :-> "server"
|
||||||
|
SHOULD_STRING("server");
|
||||||
|
const auto serverAddress = serverObject["server"].toString();
|
||||||
|
ssObject.address = serverAddress;
|
||||||
|
|
||||||
|
// port selection:
|
||||||
|
// normal: use global settings
|
||||||
|
// overriding: use current config
|
||||||
|
if (serverObject["port"].isUndefined())
|
||||||
|
{
|
||||||
|
ssObject.port = port;
|
||||||
|
}
|
||||||
|
else if (auto currPort = serverObject["port"].toInt(-1); (currPort >= 0 && currPort <= 65535))
|
||||||
|
{
|
||||||
|
ssObject.port = currPort;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ssObject.port = port;
|
||||||
|
}
|
||||||
|
|
||||||
|
// name decision:
|
||||||
|
// untitled: using server:port as name
|
||||||
|
// entitled: using given name
|
||||||
|
QString nodeName;
|
||||||
|
if (serverObject["remarks"].isUndefined())
|
||||||
|
{
|
||||||
|
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||||
|
}
|
||||||
|
else if (serverObject["remarks"].isString())
|
||||||
|
{
|
||||||
|
nodeName = serverObject["remarks"].toString();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeName = QString("%1:%2").arg(ssObject.address).arg(ssObject.port);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ratio decision:
|
||||||
|
// unspecified: ratio = 1
|
||||||
|
// specified: use given value
|
||||||
|
double ratio = 1.0;
|
||||||
|
if (auto currRatio = serverObject["ratio"].toDouble(-1.0); currRatio != -1.0)
|
||||||
|
{
|
||||||
|
ratio = currRatio;
|
||||||
|
}
|
||||||
|
// else if (!serverObject["ratio"].isUndefined())
|
||||||
|
// {
|
||||||
|
// //*logList << QObject::tr("Invalid ratio encountered. using fallback value.");
|
||||||
|
// }
|
||||||
|
|
||||||
|
// format the total name of the node.
|
||||||
|
const auto finalName = QV2RAY_SSD_DEFAULT_NAME_PATTERN.arg(*groupName, nodeName).arg(ratio);
|
||||||
|
// appending to the total list
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
outbounds.append(outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "shadowsocks", outbounds::GenerateShadowSocksOUT({ ssObject }), {}));
|
||||||
|
root["outbounds"] = outbounds;
|
||||||
|
root["inbounds"] = QJsonArray{ inbound };
|
||||||
|
serverList.append({ finalName, root });
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the current result
|
||||||
|
return serverList;
|
||||||
|
}
|
||||||
|
#undef MUST_EXIST
|
||||||
|
#undef MUST_PORT
|
||||||
|
#undef MUST_ARRAY
|
||||||
|
#undef MUST_STRING
|
||||||
|
#undef SERVER_SHOULD_BE_OBJECT
|
||||||
|
#undef SHOULD_EXIST
|
||||||
|
#undef SHOULD_STRING
|
||||||
|
} // namespace amnezia::serialization::ssd
|
||||||
|
|
||||||
@@ -0,0 +1,313 @@
|
|||||||
|
#ifndef TRANSFER_H
|
||||||
|
#define TRANSFER_H
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include "3rd/QJsonStruct/QJsonStruct.hpp"
|
||||||
|
|
||||||
|
#define JADDEx(field) root.insert(#field, field);
|
||||||
|
#define JADD(...) FOR_EACH(JADDEx, __VA_ARGS__)
|
||||||
|
|
||||||
|
constexpr auto VMESS_USER_ALTERID_DEFAULT = 0;
|
||||||
|
|
||||||
|
namespace amnezia::serialization {
|
||||||
|
|
||||||
|
struct ShadowSocksServerObject
|
||||||
|
{
|
||||||
|
QString address;
|
||||||
|
QString method;
|
||||||
|
QString password;
|
||||||
|
int port;
|
||||||
|
JSONSTRUCT_COMPARE(ShadowSocksServerObject, address, method, password)
|
||||||
|
JSONSTRUCT_REGISTER(ShadowSocksServerObject, F(address, port, method, password))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
struct VMessServerObject
|
||||||
|
{
|
||||||
|
struct UserObject
|
||||||
|
{
|
||||||
|
QString id;
|
||||||
|
int alterId = VMESS_USER_ALTERID_DEFAULT;
|
||||||
|
QString security = "auto";
|
||||||
|
int level = 0;
|
||||||
|
JSONSTRUCT_COMPARE(UserObject, id, alterId, security, level)
|
||||||
|
JSONSTRUCT_REGISTER(UserObject, F(id, alterId, security, level))
|
||||||
|
};
|
||||||
|
|
||||||
|
QString address;
|
||||||
|
int port;
|
||||||
|
QList<UserObject> users;
|
||||||
|
JSONSTRUCT_COMPARE(VMessServerObject, address, port, users)
|
||||||
|
JSONSTRUCT_REGISTER(VMessServerObject, F(address, port, users))
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
namespace transfer
|
||||||
|
{
|
||||||
|
|
||||||
|
struct HTTPRequestObject
|
||||||
|
{
|
||||||
|
QString version = "1.1";
|
||||||
|
QString method = "GET";
|
||||||
|
QList<QString> path = { "/" };
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
HTTPRequestObject()
|
||||||
|
{
|
||||||
|
headers = {
|
||||||
|
{ "Host", { "www.baidu.com", "www.bing.com" } },
|
||||||
|
{ "User-Agent",
|
||||||
|
{ "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0_2 like Mac OS X) AppleWebKit/601.1 (KHTML, like Gecko) CriOS/53.0.2785.109 Mobile/14A456 Safari/601.1.46" } },
|
||||||
|
{ "Accept-Encoding", { "gzip, deflate" } },
|
||||||
|
{ "Connection", { "keep-alive" } },
|
||||||
|
{ "Pragma", { "no-cache" } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
JSONSTRUCT_COMPARE(HTTPRequestObject, version, method, path, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HTTPRequestObject, F(version, method, path, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct HTTPResponseObject
|
||||||
|
{
|
||||||
|
QString version = "1.1";
|
||||||
|
QString status = "200";
|
||||||
|
QString reason = "OK";
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
HTTPResponseObject()
|
||||||
|
{
|
||||||
|
headers = { { "Content-Type", { "application/octet-stream", "video/mpeg" } }, //
|
||||||
|
{ "Transfer-Encoding", { "chunked" } }, //
|
||||||
|
{ "Connection", { "keep-alive" } }, //
|
||||||
|
{ "Pragma", { "no-cache" } } };
|
||||||
|
}
|
||||||
|
JSONSTRUCT_COMPARE(HTTPResponseObject, version, status, reason, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HTTPResponseObject, F(version, status, reason, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TCPHeader_Internal
|
||||||
|
{
|
||||||
|
QString type = "none";
|
||||||
|
HTTPRequestObject request;
|
||||||
|
HTTPResponseObject response;
|
||||||
|
JSONSTRUCT_COMPARE(TCPHeader_Internal, type, request, response)
|
||||||
|
JSONSTRUCT_REGISTER(TCPHeader_Internal, A(type), F(request, response))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct ObfsHeaderObject
|
||||||
|
{
|
||||||
|
QString type = "none";
|
||||||
|
JSONSTRUCT_COMPARE(ObfsHeaderObject, type)
|
||||||
|
JSONSTRUCT_REGISTER(ObfsHeaderObject, F(type))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TCPObject
|
||||||
|
{
|
||||||
|
TCPHeader_Internal header;
|
||||||
|
JSONSTRUCT_COMPARE(TCPObject, header)
|
||||||
|
JSONSTRUCT_REGISTER(TCPObject, F(header))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct KCPObject
|
||||||
|
{
|
||||||
|
int mtu = 1350;
|
||||||
|
int tti = 50;
|
||||||
|
int uplinkCapacity = 5;
|
||||||
|
int downlinkCapacity = 20;
|
||||||
|
bool congestion = false;
|
||||||
|
int readBufferSize = 2;
|
||||||
|
int writeBufferSize = 2;
|
||||||
|
QString seed;
|
||||||
|
ObfsHeaderObject header;
|
||||||
|
KCPObject(){};
|
||||||
|
JSONSTRUCT_COMPARE(KCPObject, mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, seed, header)
|
||||||
|
JSONSTRUCT_REGISTER(KCPObject, F(mtu, tti, uplinkCapacity, downlinkCapacity, congestion, readBufferSize, writeBufferSize, header, seed))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct WebSocketObject
|
||||||
|
{
|
||||||
|
QString path = "/";
|
||||||
|
QMap<QString, QString> headers;
|
||||||
|
int maxEarlyData = 0;
|
||||||
|
bool useBrowserForwarding = false;
|
||||||
|
QString earlyDataHeaderName;
|
||||||
|
JSONSTRUCT_COMPARE(WebSocketObject, path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName)
|
||||||
|
JSONSTRUCT_REGISTER(WebSocketObject, F(path, headers, maxEarlyData, useBrowserForwarding, earlyDataHeaderName))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct HttpObject
|
||||||
|
{
|
||||||
|
QList<QString> host;
|
||||||
|
QString path = "/";
|
||||||
|
QString method = "PUT";
|
||||||
|
QMap<QString, QList<QString>> headers;
|
||||||
|
JSONSTRUCT_COMPARE(HttpObject, host, path, method, headers)
|
||||||
|
JSONSTRUCT_REGISTER(HttpObject, F(host, path, method, headers))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct DomainSocketObject
|
||||||
|
{
|
||||||
|
QString path = "/";
|
||||||
|
JSONSTRUCT_COMPARE(DomainSocketObject, path)
|
||||||
|
JSONSTRUCT_REGISTER(DomainSocketObject, F(path))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct QuicObject
|
||||||
|
{
|
||||||
|
QString security = "none";
|
||||||
|
QString key;
|
||||||
|
ObfsHeaderObject header;
|
||||||
|
JSONSTRUCT_COMPARE(QuicObject, security, key, header)
|
||||||
|
JSONSTRUCT_REGISTER(QuicObject, F(security, key, header))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct gRPCObject
|
||||||
|
{
|
||||||
|
QString serviceName;
|
||||||
|
bool multiMode = false;
|
||||||
|
JSONSTRUCT_COMPARE(gRPCObject, serviceName, multiMode)
|
||||||
|
JSONSTRUCT_REGISTER(gRPCObject, F(serviceName, multiMode))
|
||||||
|
};
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct SockoptObject
|
||||||
|
{
|
||||||
|
int mark = 0;
|
||||||
|
bool tcpFastOpen = false;
|
||||||
|
QString tproxy = "off";
|
||||||
|
int tcpKeepAliveInterval = 0;
|
||||||
|
JSONSTRUCT_COMPARE(SockoptObject, mark, tcpFastOpen, tproxy, tcpKeepAliveInterval)
|
||||||
|
JSONSTRUCT_REGISTER(SockoptObject, F(mark, tcpFastOpen, tproxy, tcpKeepAliveInterval))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct CertificateObject
|
||||||
|
{
|
||||||
|
QString usage = "encipherment";
|
||||||
|
QString certificateFile;
|
||||||
|
QString keyFile;
|
||||||
|
QList<QString> certificate;
|
||||||
|
QList<QString> key;
|
||||||
|
JSONSTRUCT_COMPARE(CertificateObject, usage, certificateFile, keyFile, certificate, key)
|
||||||
|
JSONSTRUCT_REGISTER(CertificateObject, F(usage, certificateFile, keyFile, certificate, key))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TLSObject
|
||||||
|
{
|
||||||
|
QString serverName;
|
||||||
|
bool allowInsecure = false;
|
||||||
|
bool enableSessionResumption = false;
|
||||||
|
bool disableSystemRoot = false;
|
||||||
|
QList<QString> alpn;
|
||||||
|
QList<QString> pinnedPeerCertificateChainSha256;
|
||||||
|
QList<CertificateObject> certificates;
|
||||||
|
JSONSTRUCT_COMPARE(TLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||||
|
pinnedPeerCertificateChainSha256, certificates)
|
||||||
|
JSONSTRUCT_REGISTER(TLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn,
|
||||||
|
pinnedPeerCertificateChainSha256, certificates))
|
||||||
|
};
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct XTLSObject
|
||||||
|
{
|
||||||
|
QString serverName;
|
||||||
|
bool allowInsecure = false;
|
||||||
|
bool enableSessionResumption = false;
|
||||||
|
bool disableSystemRoot = false;
|
||||||
|
QList<QString> alpn;
|
||||||
|
QList<CertificateObject> certificates;
|
||||||
|
JSONSTRUCT_COMPARE(XTLSObject, serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates)
|
||||||
|
JSONSTRUCT_REGISTER(XTLSObject, F(serverName, allowInsecure, enableSessionResumption, disableSystemRoot, alpn, certificates))
|
||||||
|
};
|
||||||
|
} // namespace transfer
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
struct TrojanObject
|
||||||
|
{
|
||||||
|
quint16 port;
|
||||||
|
QString address;
|
||||||
|
QString password;
|
||||||
|
QString sni;
|
||||||
|
bool ignoreCertificate = false;
|
||||||
|
bool ignoreHostname = false;
|
||||||
|
bool reuseSession = false;
|
||||||
|
bool sessionTicket = false;
|
||||||
|
bool reusePort = false;
|
||||||
|
bool tcpFastOpen = false;
|
||||||
|
|
||||||
|
#define _X(name) json[#name] = name
|
||||||
|
QJsonObject toJson() const
|
||||||
|
{
|
||||||
|
QJsonObject json;
|
||||||
|
_X(port);
|
||||||
|
_X(address);
|
||||||
|
_X(password);
|
||||||
|
_X(sni);
|
||||||
|
_X(ignoreCertificate);
|
||||||
|
_X(ignoreHostname);
|
||||||
|
_X(reuseSession);
|
||||||
|
_X(reusePort);
|
||||||
|
_X(sessionTicket);
|
||||||
|
_X(tcpFastOpen);
|
||||||
|
return json;
|
||||||
|
};
|
||||||
|
#undef _X
|
||||||
|
|
||||||
|
#define _X(name, type) name = root[#name].to##type()
|
||||||
|
void loadJson(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
_X(port, Int);
|
||||||
|
_X(address, String);
|
||||||
|
_X(password, String);
|
||||||
|
_X(sni, String);
|
||||||
|
_X(ignoreHostname, Bool);
|
||||||
|
_X(ignoreCertificate, Bool);
|
||||||
|
_X(reuseSession, Bool);
|
||||||
|
_X(reusePort, Bool);
|
||||||
|
_X(sessionTicket, Bool);
|
||||||
|
_X(tcpFastOpen, Bool);
|
||||||
|
}
|
||||||
|
#undef _X
|
||||||
|
|
||||||
|
[[nodiscard]] static TrojanObject fromJson(const QJsonObject &root)
|
||||||
|
{
|
||||||
|
TrojanObject o;
|
||||||
|
o.loadJson(root);
|
||||||
|
return o;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct StreamSettingsObject
|
||||||
|
{
|
||||||
|
QString network = "tcp";
|
||||||
|
QString security = "none";
|
||||||
|
transfer::SockoptObject sockopt;
|
||||||
|
transfer::TLSObject tlsSettings;
|
||||||
|
transfer::XTLSObject xtlsSettings;
|
||||||
|
transfer::TCPObject tcpSettings;
|
||||||
|
transfer::KCPObject kcpSettings;
|
||||||
|
transfer::WebSocketObject wsSettings;
|
||||||
|
transfer::HttpObject httpSettings;
|
||||||
|
transfer::DomainSocketObject dsSettings;
|
||||||
|
transfer::QuicObject quicSettings;
|
||||||
|
transfer::gRPCObject grpcSettings;
|
||||||
|
JSONSTRUCT_COMPARE(StreamSettingsObject, network, security, sockopt, //
|
||||||
|
tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings)
|
||||||
|
JSONSTRUCT_REGISTER(StreamSettingsObject, F(network, security, sockopt),
|
||||||
|
F(tcpSettings, tlsSettings, xtlsSettings, kcpSettings, wsSettings, httpSettings, dsSettings, quicSettings, grpcSettings))
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif //TRANSFER_H
|
||||||
@@ -0,0 +1,271 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
#define OUTBOUND_TAG_PROXY "PROXY"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::trojan
|
||||||
|
{
|
||||||
|
|
||||||
|
const QString Serialize(const TrojanObject &object, const QString &alias)
|
||||||
|
{
|
||||||
|
|
||||||
|
QUrlQuery query;
|
||||||
|
if (object.ignoreHostname)
|
||||||
|
query.addQueryItem("allowInsecureHostname", "1");
|
||||||
|
if (object.ignoreCertificate)
|
||||||
|
query.addQueryItem("allowInsecureCertificate", "1");
|
||||||
|
if (object.sessionTicket)
|
||||||
|
query.addQueryItem("sessionTicket", "1");
|
||||||
|
if (object.ignoreCertificate || object.ignoreHostname)
|
||||||
|
query.addQueryItem("allowInsecure", "1");
|
||||||
|
if (object.tcpFastOpen)
|
||||||
|
query.addQueryItem("tfo", "1");
|
||||||
|
|
||||||
|
if (!object.sni.isEmpty())
|
||||||
|
query.addQueryItem("sni", object.sni);
|
||||||
|
|
||||||
|
QUrl link;
|
||||||
|
if (!object.password.isEmpty())
|
||||||
|
link.setUserName(object.password, QUrl::DecodedMode);
|
||||||
|
link.setPort(object.port);
|
||||||
|
link.setHost(object.address);
|
||||||
|
link.setFragment(alias);
|
||||||
|
link.setQuery(query);
|
||||||
|
link.setScheme("trojan");
|
||||||
|
|
||||||
|
return link.toString(QUrl::FullyEncoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject Deserialize(const QString &trojanUri, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
const QString prefix = "trojan://";
|
||||||
|
if (!trojanUri.startsWith(prefix))
|
||||||
|
{
|
||||||
|
*errMessage = ("Invalid Trojan URI");
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
//
|
||||||
|
const auto trueList = QStringList{ "true", "1", "yes", "y" };
|
||||||
|
const QUrl trojanUrl(trojanUri.trimmed());
|
||||||
|
const QUrlQuery query(trojanUrl.query());
|
||||||
|
*alias = trojanUrl.fragment(QUrl::FullyDecoded);
|
||||||
|
|
||||||
|
auto getQueryValue = [&](const QString &key) {
|
||||||
|
return query.queryItemValue(key, QUrl::FullyDecoded);
|
||||||
|
};
|
||||||
|
//
|
||||||
|
TrojanObject result;
|
||||||
|
result.address = trojanUrl.host();
|
||||||
|
result.password = QUrl::fromPercentEncoding(trojanUrl.userInfo().toUtf8());
|
||||||
|
result.port = trojanUrl.port();
|
||||||
|
// process sni (and also "peer")
|
||||||
|
if (query.hasQueryItem("sni"))
|
||||||
|
{
|
||||||
|
result.sni = getQueryValue("sni");
|
||||||
|
}
|
||||||
|
else if (query.hasQueryItem("peer"))
|
||||||
|
{
|
||||||
|
// This is evil and may be removed in a future version.
|
||||||
|
qWarning() << "use of 'peer' in trojan url is deprecated";
|
||||||
|
result.sni = getQueryValue("peer");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the hostname
|
||||||
|
result.sni = result.address;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
result.tcpFastOpen = trueList.contains(getQueryValue("tfo").toLower());
|
||||||
|
result.sessionTicket = trueList.contains(getQueryValue("sessionTicket").toLower());
|
||||||
|
//
|
||||||
|
bool allowAllInsecure = trueList.contains(getQueryValue("allowInsecure").toLower());
|
||||||
|
result.ignoreHostname = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureHostname").toLower());
|
||||||
|
result.ignoreCertificate = allowAllInsecure || trueList.contains(getQueryValue("allowInsecureCertificate").toLower());
|
||||||
|
|
||||||
|
QJsonObject stream;
|
||||||
|
// handle type
|
||||||
|
const auto hasType = query.hasQueryItem("type");
|
||||||
|
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||||
|
if (type != "tcp")
|
||||||
|
QJsonIO::SetValue(stream, type, "network");
|
||||||
|
|
||||||
|
|
||||||
|
// type-wise settings
|
||||||
|
if (type == "kcp")
|
||||||
|
{
|
||||||
|
const auto hasSeed = query.hasQueryItem("seed");
|
||||||
|
if (hasSeed)
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
else if (type == "http")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||||
|
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "ws")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "quic")
|
||||||
|
{
|
||||||
|
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||||
|
if (hasQuicSecurity)
|
||||||
|
{
|
||||||
|
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||||
|
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||||
|
|
||||||
|
if (quicSecurity != "none")
|
||||||
|
{
|
||||||
|
const auto key = query.queryItemValue("key");
|
||||||
|
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "grpc")
|
||||||
|
{
|
||||||
|
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||||
|
if (hasServiceName)
|
||||||
|
{
|
||||||
|
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasMode = query.hasQueryItem("mode");
|
||||||
|
if (hasMode)
|
||||||
|
{
|
||||||
|
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||||
|
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls-wise settings
|
||||||
|
const auto hasSecurity = query.hasQueryItem("security");
|
||||||
|
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||||
|
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||||
|
if (security != "none")
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, security, "security");
|
||||||
|
}
|
||||||
|
// sni
|
||||||
|
const auto hasSNI = query.hasQueryItem("sni");
|
||||||
|
if (hasSNI)
|
||||||
|
{
|
||||||
|
const auto sni = query.queryItemValue("sni");
|
||||||
|
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||||
|
}
|
||||||
|
// alpn
|
||||||
|
const auto hasALPN = query.hasQueryItem("alpn");
|
||||||
|
if (hasALPN)
|
||||||
|
{
|
||||||
|
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||||
|
QStringList aplnElems = alpnRaw.split(",");
|
||||||
|
// h2 protocol is not supported by xray
|
||||||
|
aplnElems.removeAll("h2");
|
||||||
|
if (!aplnElems.isEmpty()) {
|
||||||
|
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||||
|
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security == "reality")
|
||||||
|
{
|
||||||
|
if (query.hasQueryItem("fp"))
|
||||||
|
{
|
||||||
|
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("pbk"))
|
||||||
|
{
|
||||||
|
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("spiderX"))
|
||||||
|
{
|
||||||
|
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("sid"))
|
||||||
|
{
|
||||||
|
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject root;
|
||||||
|
QJsonArray outbounds;
|
||||||
|
QJsonObject outbound = outbounds::GenerateOutboundEntry(OUTBOUND_TAG_PROXY, "trojan", outbounds::GenerateTrojanOUT({ result }), {});
|
||||||
|
outbound["streamSettings"] = stream;
|
||||||
|
outbounds.append(outbound);
|
||||||
|
JADD(outbounds)
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace amnezia::serialization::trojan
|
||||||
|
|
||||||
@@ -0,0 +1,256 @@
|
|||||||
|
// Copyright (c) Qv2ray, A Qt frontend for V2Ray. Written in C++.
|
||||||
|
// This file is part of the Qv2ray VPN client.
|
||||||
|
//
|
||||||
|
// Qv2ray, A Qt frontend for V2Ray. Written in C++
|
||||||
|
|
||||||
|
// This program is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// This program is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Copyright (c) 2024 AmneziaVPN
|
||||||
|
// This file has been modified for AmneziaVPN
|
||||||
|
//
|
||||||
|
// This file is based on the work of the Qv2ray VPN client.
|
||||||
|
// The original code of the Qv2ray, A Qt frontend for V2Ray. Written in C++ and licensed under GPL3.
|
||||||
|
//
|
||||||
|
// The modified version of this file is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with this file. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
#include "3rd/QJsonStruct/QJsonIO.hpp"
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include "serialization.h"
|
||||||
|
|
||||||
|
namespace amnezia::serialization::vless
|
||||||
|
{
|
||||||
|
QJsonObject Deserialize(const QString &str, QString *alias, QString *errMessage)
|
||||||
|
{
|
||||||
|
// must start with vless://
|
||||||
|
if (!str.startsWith("vless://"))
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("VLESS link should start with vless://");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// parse url
|
||||||
|
QUrl url(str);
|
||||||
|
if (!url.isValid())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("link parse failed: %1").arg(url.errorString());
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch host
|
||||||
|
const auto hostRaw = url.host();
|
||||||
|
if (hostRaw.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("empty host");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
const auto host = (hostRaw.startsWith('[') && hostRaw.endsWith(']')) ? hostRaw.mid(1, hostRaw.length() - 2) : hostRaw;
|
||||||
|
|
||||||
|
// fetch port
|
||||||
|
const auto port = url.port();
|
||||||
|
if (port == -1)
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("missing port");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch remarks
|
||||||
|
const auto remarks = url.fragment();
|
||||||
|
if (!remarks.isEmpty())
|
||||||
|
{
|
||||||
|
*alias = remarks;
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetch uuid
|
||||||
|
const auto uuid = url.userInfo();
|
||||||
|
if (uuid.isEmpty())
|
||||||
|
{
|
||||||
|
*errMessage = QObject::tr("missing uuid");
|
||||||
|
return QJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize QJsonObject with basic info
|
||||||
|
QJsonObject outbound;
|
||||||
|
QJsonObject stream;
|
||||||
|
|
||||||
|
QJsonIO::SetValue(outbound, "vless", "protocol");
|
||||||
|
QJsonIO::SetValue(outbound, host, { "settings", "vnext", 0, "address" });
|
||||||
|
QJsonIO::SetValue(outbound, port, { "settings", "vnext", 0, "port" });
|
||||||
|
QJsonIO::SetValue(outbound, uuid, { "settings", "vnext", 0, "users", 0, "id" });
|
||||||
|
|
||||||
|
// parse query
|
||||||
|
QUrlQuery query(url.query());
|
||||||
|
|
||||||
|
// handle type
|
||||||
|
const auto hasType = query.hasQueryItem("type");
|
||||||
|
const auto type = hasType ? query.queryItemValue("type") : "tcp";
|
||||||
|
if (type != "tcp")
|
||||||
|
QJsonIO::SetValue(stream, type, "network");
|
||||||
|
|
||||||
|
// handle encryption
|
||||||
|
const auto hasEncryption = query.hasQueryItem("encryption");
|
||||||
|
const auto encryption = hasEncryption ? query.queryItemValue("encryption") : "none";
|
||||||
|
QJsonIO::SetValue(outbound, encryption, { "settings", "vnext", 0, "users", 0, "encryption" });
|
||||||
|
|
||||||
|
// type-wise settings
|
||||||
|
if (type == "kcp")
|
||||||
|
{
|
||||||
|
const auto hasSeed = query.hasQueryItem("seed");
|
||||||
|
if (hasSeed)
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("seed"), { "kcpSettings", "seed" });
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "kcpSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
else if (type == "http")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "httpSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
const auto hosts = QJsonArray::fromStringList(query.queryItemValue("host").split(","));
|
||||||
|
QJsonIO::SetValue(stream, hosts, { "httpSettings", "host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "ws")
|
||||||
|
{
|
||||||
|
const auto hasPath = query.hasQueryItem("path");
|
||||||
|
const auto path = hasPath ? QUrl::fromPercentEncoding(query.queryItemValue("path").toUtf8()) : "/";
|
||||||
|
if (path != "/")
|
||||||
|
QJsonIO::SetValue(stream, path, { "wsSettings", "path" });
|
||||||
|
|
||||||
|
const auto hasHost = query.hasQueryItem("host");
|
||||||
|
if (hasHost)
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, query.queryItemValue("host"), { "wsSettings", "headers", "Host" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "quic")
|
||||||
|
{
|
||||||
|
const auto hasQuicSecurity = query.hasQueryItem("quicSecurity");
|
||||||
|
if (hasQuicSecurity)
|
||||||
|
{
|
||||||
|
const auto quicSecurity = query.queryItemValue("quicSecurity");
|
||||||
|
QJsonIO::SetValue(stream, quicSecurity, { "quicSettings", "security" });
|
||||||
|
|
||||||
|
if (quicSecurity != "none")
|
||||||
|
{
|
||||||
|
const auto key = query.queryItemValue("key");
|
||||||
|
QJsonIO::SetValue(stream, key, { "quicSettings", "key" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasHeaderType = query.hasQueryItem("headerType");
|
||||||
|
const auto headerType = hasHeaderType ? query.queryItemValue("headerType") : "none";
|
||||||
|
if (headerType != "none")
|
||||||
|
QJsonIO::SetValue(stream, headerType, { "quicSettings", "header", "type" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (type == "grpc")
|
||||||
|
{
|
||||||
|
const auto hasServiceName = query.hasQueryItem("serviceName");
|
||||||
|
if (hasServiceName)
|
||||||
|
{
|
||||||
|
const auto serviceName = QUrl::fromPercentEncoding(query.queryItemValue("serviceName").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, serviceName, { "grpcSettings", "serviceName" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto hasMode = query.hasQueryItem("mode");
|
||||||
|
if (hasMode)
|
||||||
|
{
|
||||||
|
const auto multiMode = QUrl::fromPercentEncoding(query.queryItemValue("mode").toUtf8()) == "multi";
|
||||||
|
QJsonIO::SetValue(stream, multiMode, { "grpcSettings", "multiMode" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tls-wise settings
|
||||||
|
const auto hasSecurity = query.hasQueryItem("security");
|
||||||
|
const auto security = hasSecurity ? query.queryItemValue("security") : "none";
|
||||||
|
const auto tlsKey = security == "xtls" ? "xtlsSettings" : ( security == "tls" ? "tlsSettings" : "realitySettings" );
|
||||||
|
if (security != "none")
|
||||||
|
{
|
||||||
|
QJsonIO::SetValue(stream, security, "security");
|
||||||
|
}
|
||||||
|
// sni
|
||||||
|
const auto hasSNI = query.hasQueryItem("sni");
|
||||||
|
if (hasSNI)
|
||||||
|
{
|
||||||
|
const auto sni = query.queryItemValue("sni");
|
||||||
|
QJsonIO::SetValue(stream, sni, { tlsKey, "serverName" });
|
||||||
|
}
|
||||||
|
// alpn
|
||||||
|
const auto hasALPN = query.hasQueryItem("alpn");
|
||||||
|
if (hasALPN)
|
||||||
|
{
|
||||||
|
const auto alpnRaw = QUrl::fromPercentEncoding(query.queryItemValue("alpn").toUtf8());
|
||||||
|
QStringList aplnElems = alpnRaw.split(",");
|
||||||
|
// h2 protocol is not supported by xray
|
||||||
|
aplnElems.removeAll("h2");
|
||||||
|
if (!aplnElems.isEmpty()) {
|
||||||
|
const auto alpnArray = QJsonArray::fromStringList(aplnElems);
|
||||||
|
QJsonIO::SetValue(stream, alpnArray, { tlsKey, "alpn" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// xtls-specific
|
||||||
|
if (security == "xtls" || security == "reality")
|
||||||
|
{
|
||||||
|
const auto flow = query.queryItemValue("flow");
|
||||||
|
QJsonIO::SetValue(outbound, flow, { "settings", "vnext", 0, "users", 0, "flow" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (security == "reality")
|
||||||
|
{
|
||||||
|
if (query.hasQueryItem("fp"))
|
||||||
|
{
|
||||||
|
const auto fp = QUrl::fromPercentEncoding(query.queryItemValue("fp").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, fp, { "realitySettings", "fingerprint" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("pbk"))
|
||||||
|
{
|
||||||
|
const auto pbk = QUrl::fromPercentEncoding(query.queryItemValue("pbk").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, pbk, { "realitySettings", "publicKey" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("spiderX"))
|
||||||
|
{
|
||||||
|
const auto spiderX = QUrl::fromPercentEncoding(query.queryItemValue("spiderX").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, spiderX, { "realitySettings", "spiderX" });
|
||||||
|
}
|
||||||
|
if (query.hasQueryItem("sid"))
|
||||||
|
{
|
||||||
|
const auto sid = QUrl::fromPercentEncoding(query.queryItemValue("sid").toUtf8());
|
||||||
|
QJsonIO::SetValue(stream, sid, { "realitySettings", "shortId" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// assembling config
|
||||||
|
QJsonObject root;
|
||||||
|
outbound["streamSettings"] = stream;
|
||||||
|
QJsonObject inbound = inbounds::GenerateInboundEntry();
|
||||||
|
root["outbounds"] = QJsonArray{ outbound };
|
||||||
|
root["inbounds"] = QJsonArray { inbound };
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
} // namespace amnezia::serialization::vless
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user