Merge branch 'dev' into ios-wireguard

This commit is contained in:
pokamest 2021-11-30 21:51:06 +03:00
commit bf8b3c3b2f
45 changed files with 1063 additions and 522 deletions

View file

@ -17,338 +17,25 @@
#include "android_vpnprotocol.h"
#include "core/errorstrings.h"
// Binder Codes for VPNServiceBinder
// See also - VPNServiceBinder.kt
// Actions that are Requestable
const int ACTION_ACTIVATE = 1;
const int ACTION_DEACTIVATE = 2;
const int ACTION_REGISTERLISTENER = 3;
const int ACTION_REQUEST_STATISTIC = 4;
const int ACTION_REQUEST_GET_LOG = 5;
const int ACTION_REQUEST_CLEANUP_LOG = 6;
const int ACTION_RESUME_ACTIVATE = 7;
const int ACTION_SET_NOTIFICATION_TEXT = 8;
const int ACTION_SET_NOTIFICATION_FALLBACK = 9;
#include "platforms/android/android_controller.h"
// Event Types that will be Dispatched after registration
const int EVENT_INIT = 0;
const int EVENT_CONNECTED = 1;
const int EVENT_DISCONNECTED = 2;
const int EVENT_STATISTIC_UPDATE = 3;
const int EVENT_BACKEND_LOGS = 4;
const int EVENT_ACTIVATION_ERROR = 5;
namespace {
AndroidVpnProtocol* s_instance = nullptr;
constexpr auto PERMISSIONHELPER_CLASS =
"org/amnezia/vpn/qt/VPNPermissionHelper";
} // namespace
AndroidVpnProtocol::AndroidVpnProtocol(Proto protocol, const QJsonObject &configuration, QObject* parent)
: VpnProtocol(configuration, parent),
m_protocol(protocol),
m_binder(this)
m_protocol(protocol)
{
}
AndroidVpnProtocol* AndroidVpnProtocol::instance() {
return s_instance;
}
bool AndroidVpnProtocol::initialize()
{
qDebug() << "Initializing";
// Hook in the native implementation for startActivityForResult into the JNI
JNINativeMethod methods[]{{"startActivityForResult",
"(Landroid/content/Intent;)V",
reinterpret_cast<void*>(startActivityForResult)}};
QAndroidJniObject javaClass(PERMISSIONHELPER_CLASS);
QAndroidJniEnvironment env;
jclass objectClass = env->GetObjectClass(javaClass.object<jobject>());
env->RegisterNatives(objectClass, methods,
sizeof(methods) / sizeof(methods[0]));
env->DeleteLocalRef(objectClass);
auto appContext = QtAndroid::androidActivity().callObjectMethod(
"getApplicationContext", "()Landroid/content/Context;");
QAndroidJniObject::callStaticMethod<void>(
"org/amnezia/vpn/VPNService", "startService",
"(Landroid/content/Context;)V", appContext.object());
// Start the VPN Service (if not yet) and Bind to it
const bool bindResult = QtAndroid::bindService(
QAndroidIntent(appContext.object(), "org.amnezia.vpn.VPNService"),
*this, QtAndroid::BindFlag::AutoCreate);
qDebug() << "Binding to the service..." << bindResult;
return bindResult;
}
ErrorCode AndroidVpnProtocol::start()
{
//qDebug().noquote() << "AndroidVpnProtocol::start" << QJsonDocument(m_rawConfig).toJson();
qDebug() << "Prompting for VPN permission";
auto appContext = QtAndroid::androidActivity().callObjectMethod(
"getApplicationContext", "()Landroid/content/Context;");
QAndroidJniObject::callStaticMethod<void>(
PERMISSIONHELPER_CLASS, "startService", "(Landroid/content/Context;)V",
appContext.object());
// QJsonObject jServer;
// jServer["ipv4AddrIn"] = server.ipv4AddrIn();
// jServer["ipv4Gateway"] = server.ipv4Gateway();
// jServer["ipv6AddrIn"] = server.ipv6AddrIn();
// jServer["ipv6Gateway"] = server.ipv6Gateway();
// jServer["publicKey"] = server.publicKey();
// jServer["port"] = (int)server.choosePort();
// QJsonArray allowedIPs;
// foreach (auto item, allowedIPAddressRanges) {
// QJsonValue val;
// val = item.toString();
// allowedIPs.append(val);
// }
// QJsonArray excludedApps;
// foreach (auto appID, vpnDisabledApps) {
// excludedApps.append(QJsonValue(appID));
// }
// QJsonObject args;
// args["device"] = jDevice;
// args["keys"] = jKeys;
// args["server"] = jServer;
// args["reason"] = (int)reason;
// args["allowedIPs"] = allowedIPs;
// args["excludedApps"] = excludedApps;
// args["dns"] = dns.toString();
QAndroidParcel sendData;
sendData.writeData(QJsonDocument(m_rawConfig).toJson());
bool activateResult = false;
while (!activateResult){
activateResult = m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr);
}
return activateResult ? NoError : UnknownError;
AndroidController::instance()->setVpnConfig(m_rawConfig);
return AndroidController::instance()->start();
}
// Activates the tunnel that is currently set
// in the VPN Service
void AndroidVpnProtocol::resume_start() {
QAndroidParcel nullData;
m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr);
}
void AndroidVpnProtocol::stop() {
qDebug() << "deactivation";
// if (reason != ReasonNone) {
// // Just show that we're disconnected
// // we're doing the actual disconnect once
// // the vpn-service has the new server ready in Action->Activate
// emit disconnected();
// logger.warning() << "deactivation skipped for Switching";
// return;
// }
QAndroidParcel nullData;
m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr);
}
/*
* Sets the current notification text that is shown
*/
void AndroidVpnProtocol::setNotificationText(const QString& title,
const QString& message,
int timerSec) {
QJsonObject args;
args["title"] = title;
args["message"] = message;
args["sec"] = timerSec;
QJsonDocument doc(args);
QAndroidParcel data;
data.writeData(doc.toJson());
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr);
}
/*
* Sets fallback Notification text that should be shown in case the VPN
* switches into the Connected state without the app open
* e.g via always-on vpn
*/
void AndroidVpnProtocol::setFallbackConnectedNotification() {
QJsonObject args;
args["title"] = qtTrId("vpn.main.productName");
//% "Ready for you to connect"
//: Refers to the app - which is currently running the background and waiting
args["message"] = qtTrId("vpn.android.notification.isIDLE");
QJsonDocument doc(args);
QAndroidParcel data;
data.writeData(doc.toJson());
m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr);
}
void AndroidVpnProtocol::checkStatus() {
qDebug() << "check status";
QAndroidParcel nullParcel;
m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr);
}
void AndroidVpnProtocol::getBackendLogs(std::function<void(const QString&)>&& a_callback) {
qDebug() << "get logs";
m_logCallback = std::move(a_callback);
QAndroidParcel nullData, replyData;
m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData);
}
void AndroidVpnProtocol::cleanupBackendLogs() {
qDebug() << "cleanup logs";
QAndroidParcel nullParcel;
m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr);
}
void AndroidVpnProtocol::onServiceConnected(
const QString& name, const QAndroidBinder& serviceBinder) {
qDebug() << "Server " + name + " connected";
Q_UNUSED(name);
m_serviceBinder = serviceBinder;
// Send the Service our Binder to recive incoming Events
QAndroidParcel binderParcel;
binderParcel.writeBinder(m_binder);
m_serviceBinder.transact(ACTION_REGISTERLISTENER, binderParcel, nullptr);
}
void AndroidVpnProtocol::onServiceDisconnected(const QString& name) {
qDebug() << "Server disconnected";
m_serviceConnected = false;
Q_UNUSED(name);
// TODO: Maybe restart? Or crash?
}
/**
* @brief AndroidController::VPNBinder::onTransact
* @param code the Event-Type we get From the VPNService See
* @param data - Might contain UTF-8 JSON in case the Event has a payload
* @param reply - always null
* @param flags - unused
* @return Returns true is the code was a valid Event Code
*/
bool AndroidVpnProtocol::VPNBinder::onTransact(int code,
const QAndroidParcel& data,
const QAndroidParcel& reply,
QAndroidBinder::CallType flags) {
Q_UNUSED(data);
Q_UNUSED(reply);
Q_UNUSED(flags);
QJsonDocument doc;
QString buffer;
switch (code) {
case EVENT_INIT:
qDebug() << "Transact: init";
doc = QJsonDocument::fromJson(data.readData());
emit m_controller->initialized(
true, doc.object()["connected"].toBool(),
QDateTime::fromMSecsSinceEpoch(
doc.object()["time"].toVariant().toLongLong()));
// Pass a localised version of the Fallback string for the Notification
m_controller->setFallbackConnectedNotification();
break;
case EVENT_CONNECTED:
qDebug() << "Transact: connected";
m_controller->setConnectionState(Connected);
break;
case EVENT_DISCONNECTED:
qDebug() << "Transact: disconnected";
m_controller->setConnectionState(Disconnected);
break;
case EVENT_STATISTIC_UPDATE:
qDebug() << "Transact:: update";
// Data is here a JSON String
doc = QJsonDocument::fromJson(data.readData());
// TODO update counters
// emit m_controller->statusUpdated(doc.object()["endpoint"].toString(),
// doc.object()["deviceIpv4"].toString(),
// doc.object()["totalTX"].toInt(),
// doc.object()["totalRX"].toInt());
break;
case EVENT_BACKEND_LOGS:
qDebug() << "Transact: backend logs";
buffer = readUTF8Parcel(data);
if (m_controller->m_logCallback) {
m_controller->m_logCallback(buffer);
}
break;
case EVENT_ACTIVATION_ERROR:
m_controller->setConnectionState(Error);
default:
qWarning() << "Transact: Invalid!";
break;
}
return true;
}
QString AndroidVpnProtocol::VPNBinder::readUTF8Parcel(QAndroidParcel data) {
// 106 is the Code for UTF-8
return QTextCodec::codecForMib(106)->toUnicode(data.readData());
}
const int ACTIVITY_RESULT_OK = 0xffffffff;
/**
* @brief Starts the Given intent in Context of the QTActivity
* @param env
* @param intent
*/
void AndroidVpnProtocol::startActivityForResult(JNIEnv *env, jobject, jobject intent)
void AndroidVpnProtocol::stop()
{
qDebug() << "start activity";
Q_UNUSED(env);
QtAndroid::startActivity(intent, 1337,
[](int receiverRequestCode, int resultCode,
const QAndroidJniObject& data) {
// Currently this function just used in
// VPNService.kt::checkPersmissions. So the result
// we're getting is if the User gave us the
// Vpn.bind permission. In case of NO we should
// abort.
Q_UNUSED(receiverRequestCode);
Q_UNUSED(data);
AndroidVpnProtocol* controller =
AndroidVpnProtocol::instance();
if (!controller) {
return;
}
if (resultCode == ACTIVITY_RESULT_OK) {
qDebug() << "VPN PROMPT RESULT - Accepted";
controller->resume_start();
return;
}
// If the request got rejected abort the current
// connection.
qWarning() << "VPN PROMPT RESULT - Rejected";
controller->setConnectionState(Disconnected);
});
return;
qDebug() << "AndroidVpnProtocol::stop()";
AndroidController::instance()->stop();
}

View file

@ -11,38 +11,16 @@ using namespace amnezia;
class AndroidVpnProtocol : public VpnProtocol,
public QAndroidServiceConnection
class AndroidVpnProtocol : public VpnProtocol
{
Q_OBJECT
public:
explicit AndroidVpnProtocol(Proto protocol, const QJsonObject& configuration, QObject* parent = nullptr);
static AndroidVpnProtocol* instance();
virtual ~AndroidVpnProtocol() override = default;
bool initialize();
virtual ErrorCode start() override;
virtual void stop() override;
void resume_start();
void checkStatus();
void setNotificationText(const QString& title, const QString& message,
int timerSec);
void setFallbackConnectedNotification();
void getBackendLogs(std::function<void(const QString&)>&& callback);
void cleanupBackendLogs();
// from QAndroidServiceConnection
void onServiceConnected(const QString& name,
const QAndroidBinder& serviceBinder) override;
void onServiceDisconnected(const QString& name) override;
ErrorCode start() override;
void stop() override;
signals:
@ -55,28 +33,6 @@ protected:
private:
Proto m_protocol;
bool m_serviceConnected = false;
std::function<void(const QString&)> m_logCallback;
QAndroidBinder m_serviceBinder;
class VPNBinder : public QAndroidBinder {
public:
VPNBinder(AndroidVpnProtocol* controller) : m_controller(controller) {}
bool onTransact(int code, const QAndroidParcel& data,
const QAndroidParcel& reply,
QAndroidBinder::CallType flags) override;
QString readUTF8Parcel(QAndroidParcel data);
private:
AndroidVpnProtocol* m_controller = nullptr;
};
VPNBinder m_binder;
static void startActivityForResult(JNIEnv* env, jobject /*thiz*/,
jobject intent);
};
#endif // ANDROID_VPNPROTOCOL_H

View file

@ -241,7 +241,7 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE
void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration)
{
m_config = configuration.value(ProtocolProps::key_proto_config_data(Protocol::Ikev2)).toObject();
m_config = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject();
}
ErrorCode Ikev2Protocol::start()

View file

@ -78,7 +78,7 @@ ErrorCode OpenVpnOverCloakProtocol::start()
m_ckProcess.waitForStarted();
if (m_ckProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(ConnectionState::Connecting);
setConnectionState(VpnConnectionState::Connecting);
return OpenVpnProtocol::start();
}
@ -113,5 +113,5 @@ QString OpenVpnOverCloakProtocol::cloakExecPath()
void OpenVpnOverCloakProtocol::readCloakConfiguration(const QJsonObject &configuration)
{
m_cloakConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::Cloak)).toObject();
m_cloakConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::Cloak)).toObject();
}

View file

@ -86,8 +86,8 @@ void OpenVpnProtocol::killOpenVpnProcess()
void OpenVpnProtocol::readOpenVpnConfiguration(const QJsonObject &configuration)
{
if (configuration.contains(ProtocolProps::key_proto_config_data(Protocol::OpenVpn))) {
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::OpenVpn)).toObject();
if (configuration.contains(ProtocolProps::key_proto_config_data(Proto::OpenVpn))) {
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::OpenVpn)).toObject();
m_configFile.open();
m_configFile.write(jConfig.value(config_key::config).toString().toUtf8());
@ -167,7 +167,7 @@ ErrorCode OpenVpnProtocol::start()
return lastError();
}
setConnectionState(ConnectionState::Connecting);
setConnectionState(VpnConnectionState::Connecting);
m_openVpnProcess = IpcClient::CreatePrivilegedProcess();
@ -201,7 +201,7 @@ ErrorCode OpenVpnProtocol::start()
});
connect(m_openVpnProcess.data(), &PrivilegedProcess::finished, this, [&]() {
setConnectionState(ConnectionState::Disconnected);
setConnectionState(VpnConnectionState::Disconnected);
});
m_openVpnProcess->start();

View file

@ -75,7 +75,7 @@ ErrorCode ShadowSocksVpnProtocol::start()
m_ssProcess.waitForStarted();
if (m_ssProcess.state() == QProcess::ProcessState::Running) {
setConnectionState(ConnectionState::Connecting);
setConnectionState(VpnConnectionState::Connecting);
return OpenVpnProtocol::start();
}
@ -112,5 +112,5 @@ QString ShadowSocksVpnProtocol::shadowSocksExecPath()
void ShadowSocksVpnProtocol::readShadowSocksConfiguration(const QJsonObject &configuration)
{
m_shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::ShadowSocks)).toObject();
m_shadowSocksConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::ShadowSocks)).toObject();
}

View file

@ -48,24 +48,18 @@ signals:
void timeoutTimerEvent();
void protocolError(amnezia::ErrorCode e);
// This signal is emitted when the controller is initialized. Note that the
// VPN tunnel can be already active. In this case, "connected" should be set
// to true and the "connectionDate" should be set to the activation date if
// known.
// If "status" is set to false, the backend service is considered unavailable.
void initialized(bool status, bool connected,
const QDateTime& connectionDate);
protected slots:
virtual void onTimeout();
public slots:
virtual void onTimeout(); // todo: remove?
void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
void setConnectionState(VpnProtocol::VpnConnectionState state);
protected:
void startTimeoutTimer();
void stopTimeoutTimer();
virtual void setBytesChanged(quint64 receivedBytes, quint64 sentBytes);
virtual void setConnectionState(VpnProtocol::VpnConnectionState state);
VpnConnectionState m_connectionState;
QString m_routeGateway;
QString m_vpnLocalAddress;
QString m_vpnGateway;

View file

@ -61,7 +61,7 @@ void WireguardProtocol::stop()
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
qDebug() << "WireguardProtocol::WireguardProtocol Stop errorOccurred" << error;
setConnectionState(ConnectionState::Disconnected);
setConnectionState(VpnConnectionState::Disconnected);
});
connect(m_wireguardStopProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) {
@ -79,7 +79,7 @@ void WireguardProtocol::stop()
void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configuration)
{
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Protocol::WireGuard)).toObject();
QJsonObject jConfig = configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toObject();
if (!m_configFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
qCritical() << "Failed to save wireguard config to" << m_configFile.fileName();
@ -93,7 +93,7 @@ void WireguardProtocol::readWireguardConfiguration(const QJsonObject &configurat
m_configFileName = m_configFile.fileName();
qDebug().noquote() << QString("Set config data") << m_configFileName;
qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Protocol::WireGuard)).toString().toUtf8();
qDebug().noquote() << QString("Set config data") << configuration.value(ProtocolProps::key_proto_config_data(Proto::WireGuard)).toString().toUtf8();
}
@ -151,7 +151,7 @@ ErrorCode WireguardProtocol::start()
return lastError();
}
setConnectionState(ConnectionState::Connecting);
setConnectionState(VpnConnectionState::Connecting);
m_wireguardStartProcess = IpcClient::CreatePrivilegedProcess();
@ -178,7 +178,7 @@ ErrorCode WireguardProtocol::start()
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::errorOccurred, this, [this](QProcess::ProcessError error) {
qDebug() << "WireguardProtocol::WireguardProtocol errorOccurred" << error;
setConnectionState(ConnectionState::Disconnected);
setConnectionState(VpnConnectionState::Disconnected);
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::stateChanged, this, [this](QProcess::ProcessState newState) {
@ -186,7 +186,7 @@ ErrorCode WireguardProtocol::start()
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::finished, this, [this]() {
setConnectionState(ConnectionState::Connected);
setConnectionState(VpnConnectionState::Connected);
});
connect(m_wireguardStartProcess.data(), &PrivilegedProcess::readyRead, this, [this]() {