Compare commits
28 Commits
dev-killsw
...
2.0.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c19f34570d | ||
|
|
e9f44ffcc6 | ||
|
|
a261ab4f0c | ||
|
|
39de79d3cd | ||
|
|
40ab540179 | ||
|
|
7d7b6f4475 | ||
|
|
912051637a | ||
|
|
c233f767f4 | ||
|
|
b63bf2465f | ||
|
|
505c9c6218 | ||
|
|
cb21991efa | ||
|
|
49e58c25f2 | ||
|
|
ac6000a2ae | ||
|
|
6d1c9edc24 | ||
|
|
1a7fa3746d | ||
|
|
f7a57ffeb4 | ||
|
|
ad9d45a154 | ||
|
|
5fffb2afe3 | ||
|
|
1269114074 | ||
|
|
d24f6ae064 | ||
|
|
95fe09489c | ||
|
|
1a144da36d | ||
|
|
8e26da1759 | ||
|
|
daf53226c3 | ||
|
|
2b9e615e51 | ||
|
|
02acbecef5 | ||
|
|
8f28964ce2 | ||
|
|
495e74e2f0 |
@@ -2,5 +2,5 @@ TEMPLATE = subdirs
|
||||
SUBDIRS = client
|
||||
|
||||
!ios:!android {
|
||||
SUBDIRS += service platform
|
||||
SUBDIRS += service
|
||||
}
|
||||
|
||||
@@ -940,8 +940,8 @@ void SshConnectionPrivate::connectToHost()
|
||||
this, &SshConnectionPrivate::handleSocketConnected);
|
||||
connect(m_socket, &QIODevice::readyRead,
|
||||
this, &SshConnectionPrivate::handleIncomingData);
|
||||
connect(m_socket, &QAbstractSocket::errorOccurred,
|
||||
this, &SshConnectionPrivate::handleSocketError);
|
||||
//connect(m_socket, &QAbstractSocket::errorOccurred,
|
||||
// this, &SshConnectionPrivate::handleSocketError);
|
||||
connect(m_socket, &QAbstractSocket::disconnected,
|
||||
this, &SshConnectionPrivate::handleSocketDisconnected);
|
||||
connect(&m_timeoutTimer, &QTimer::timeout, this, &SshConnectionPrivate::handleTimeout);
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version="1.0"?>
|
||||
<manifest package="org.amnezia.vpn" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="-- %%INSERT_VERSION_NAME%% --" android:versionCode="-- %%INSERT_VERSION_CODE%% --" android:installLocation="auto">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||
<uses-permission android:name="android.permission.CAMERA"/>
|
||||
|
||||
@@ -15,27 +15,15 @@
|
||||
<!-- %%INSERT_FEATURES -->
|
||||
|
||||
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
|
||||
<application
|
||||
android:hardwareAccelerated="true"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:extractNativeLibs="true"
|
||||
android:icon="@drawable/icon">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density"
|
||||
android:name="org.qtproject.qt5.android.bindings.QtActivity"
|
||||
|
||||
android:label="-- %%INSERT_APP_NAME%% --"
|
||||
android:screenOrientation="unspecified"
|
||||
android:launchMode="singleTop">
|
||||
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="-- %%INSERT_APP_NAME%% --" android:extractNativeLibs="true" android:icon="@drawable/icon">
|
||||
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="-- %%INSERT_APP_NAME%% --" android:screenOrientation="unspecified" android:launchMode="singleTop" android:theme="@style/splashScreenTheme">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
|
||||
<!-- Application arguments -->
|
||||
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
|
||||
<!-- Application arguments -->
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="-- %%INSERT_APP_LIB_NAME%% --"/>
|
||||
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
|
||||
<meta-data android:name="android.app.repository" android:value="default"/>
|
||||
@@ -43,7 +31,6 @@
|
||||
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
|
||||
<!-- Deploy Qt libs as part of package -->
|
||||
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/>
|
||||
|
||||
<!-- Run with local libs -->
|
||||
<meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/>
|
||||
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
|
||||
@@ -58,7 +45,6 @@
|
||||
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
|
||||
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
|
||||
<!-- Messages maps -->
|
||||
|
||||
<!-- Splash screen -->
|
||||
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
|
||||
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
|
||||
@@ -69,7 +55,6 @@
|
||||
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
|
||||
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
|
||||
<!-- Splash screen -->
|
||||
|
||||
<!-- Background running -->
|
||||
<!-- Warning: changing this value to true may cause unexpected crashes if the
|
||||
application still try to draw after
|
||||
@@ -77,11 +62,9 @@
|
||||
signal is sent! -->
|
||||
<meta-data android:name="android.app.background_running" android:value="false"/>
|
||||
<!-- Background running -->
|
||||
|
||||
<!-- auto screen scale factor -->
|
||||
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
|
||||
<!-- auto screen scale factor -->
|
||||
|
||||
<!-- extract android style -->
|
||||
<!-- available android:values :
|
||||
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
|
||||
@@ -92,24 +75,20 @@
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
|
||||
<!-- extract android style -->
|
||||
<meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/splashscreen"/>
|
||||
</activity>
|
||||
|
||||
<service android:name=".VPNService"
|
||||
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
<service android:name="org.amnezia.vpn.qt.VPNPermissionHelper"
|
||||
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
</service>
|
||||
|
||||
|
||||
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</activity>
|
||||
|
||||
<service android:name=".VPNService"
|
||||
android:process=":QtOnlyProcess"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service android:name="org.amnezia.vpn.qt.VPNPermissionHelper"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||
</service>
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -102,8 +102,8 @@ android {
|
||||
resConfig "en"
|
||||
minSdkVersion = 24
|
||||
targetSdkVersion = 30
|
||||
versionCode 7 // Change to a higher number
|
||||
versionName "2.0.7" // Change to a higher number
|
||||
versionCode 8 // Change to a higher number
|
||||
versionName "2.0.8" // Change to a higher number
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
@@ -37,12 +37,13 @@ class VPNService : android.net.VpnService() {
|
||||
SharedLibraryLoader.loadSharedLibrary(this, "ovpn3")
|
||||
Log.i(tag, "Loaded libs")
|
||||
Log.e(tag, "Wireguard Version ${wgVersion()}")
|
||||
mOpenVPNThreadv3 = OpenVPNThreadv3 (this)
|
||||
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
|
||||
mAlreadyInitialised = true
|
||||
|
||||
}
|
||||
|
||||
override fun onUnbind(intent: Intent?): Boolean {
|
||||
Log.v(tag, "Got Unbind request")
|
||||
if (!isUp) {
|
||||
// If the Qt Client got closed while we were not connected
|
||||
// we do not need to stay as a foreground service.
|
||||
@@ -52,9 +53,9 @@ class VPNService : android.net.VpnService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* EntryPoint for the Service, gets Called when AndroidController.cpp
|
||||
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
|
||||
*/
|
||||
* EntryPoint for the Service, gets Called when AndroidController.cpp
|
||||
* calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it.
|
||||
*/
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
Log.v(tag, "Got Bind request")
|
||||
init()
|
||||
@@ -62,10 +63,10 @@ class VPNService : android.net.VpnService() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Might be the entryPoint if the Service gets Started via an
|
||||
* Service Intent: Might be from Always-On-Vpn from Settings
|
||||
* or from Booting the device and having "connect on boot" enabled.
|
||||
*/
|
||||
* Might be the entryPoint if the Service gets Started via an
|
||||
* Service Intent: Might be from Always-On-Vpn from Settings
|
||||
* or from Booting the device and having "connect on boot" enabled.
|
||||
*/
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
init()
|
||||
intent?.let {
|
||||
@@ -84,28 +85,28 @@ class VPNService : android.net.VpnService() {
|
||||
Log.e(
|
||||
tag,
|
||||
"VPN service was triggered without defining a Server or having a tunnel"
|
||||
)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
this.mConfig = JSONObject(lastConfString)
|
||||
)
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
this.mConfig = JSONObject(lastConfString)
|
||||
}
|
||||
|
||||
// Invoked when the application is revoked.
|
||||
// At this moment, the VPN interface is already deactivated by the system.
|
||||
override fun onRevoke() {
|
||||
this.turnOff()
|
||||
super.onRevoke()
|
||||
}
|
||||
return super.onStartCommand(intent, flags, startId)
|
||||
}
|
||||
|
||||
var connectionTime: Long = 0
|
||||
// Invoked when the application is revoked.
|
||||
// At this moment, the VPN interface is already deactivated by the system.
|
||||
override fun onRevoke() {
|
||||
this.turnOff()
|
||||
super.onRevoke()
|
||||
}
|
||||
|
||||
var connectionTime: Long = 0
|
||||
get() {
|
||||
return mConnectionTime
|
||||
}
|
||||
|
||||
var isUp: Boolean
|
||||
var isUp: Boolean
|
||||
get() {
|
||||
return currentTunnelHandle >= 0
|
||||
}
|
||||
@@ -118,7 +119,7 @@ class VPNService : android.net.VpnService() {
|
||||
mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "")
|
||||
mConnectionTime = 0
|
||||
}
|
||||
val status: JSONObject
|
||||
val status: JSONObject
|
||||
get() {
|
||||
val deviceIpv4: String = ""
|
||||
return JSONObject().apply {
|
||||
@@ -128,299 +129,307 @@ class VPNService : android.net.VpnService() {
|
||||
putOpt("deviceIpv4", mConfig?.getJSONObject("device")?.getString("ipv4Address"))
|
||||
}
|
||||
}
|
||||
/*
|
||||
* Checks if the VPN Permission is given.
|
||||
* If the permission is given, returns true
|
||||
* Requests permission and returns false if not.
|
||||
*/
|
||||
fun checkPermissions(): Boolean {
|
||||
// See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service
|
||||
// Call Prepare, if we get an Intent back, we dont have the VPN Permission
|
||||
// from the user. So we need to pass this to our main Activity and exit here.
|
||||
val intent = prepare(this)
|
||||
if (intent == null) {
|
||||
Log.e(tag, "VPN Permission Already Present")
|
||||
return true
|
||||
}
|
||||
Log.e(tag, "Requesting VPN Permission")
|
||||
return false
|
||||
}
|
||||
|
||||
fun turnOn(json: JSONObject?): Int {
|
||||
if (!checkPermissions()) {
|
||||
Log.e(tag, "turn on was called without no permissions present!")
|
||||
isUp = false
|
||||
return 0
|
||||
}
|
||||
Log.i(tag, "Permission okay")
|
||||
mConfig = json!!
|
||||
mProtocol = mConfig!!.getString("protocol")
|
||||
when (mProtocol) {
|
||||
"openvpn" -> startOpenVpn()
|
||||
"wireguard" -> startWireGuard()
|
||||
else -> {
|
||||
Log.e(tag, "No protocol")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
fun establish(): ParcelFileDescriptor? {
|
||||
mbuilder.allowFamily(OsConstants.AF_INET)
|
||||
mbuilder.allowFamily(OsConstants.AF_INET6)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mbuilder.setMetered(false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
|
||||
|
||||
return mbuilder.establish()
|
||||
}
|
||||
|
||||
fun setMtu(mtu: Int) {
|
||||
mbuilder.setMtu(mtu)
|
||||
}
|
||||
|
||||
fun addAddress(ip: String, len: Int){
|
||||
Log.v(tag, "mbuilder.addAddress($ip, $len)")
|
||||
mbuilder.addAddress(ip, len)
|
||||
}
|
||||
|
||||
fun addRoute(ip: String, len: Int){
|
||||
Log.v(tag, "mbuilder.addRoute($ip, $len)")
|
||||
mbuilder.addRoute(ip, len)
|
||||
}
|
||||
|
||||
fun addDNS(ip: String){
|
||||
Log.v(tag, "mbuilder.addDnsServer($ip)")
|
||||
mbuilder.addDnsServer(ip)
|
||||
if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
|
||||
mbuilder.addRoute(ip, 32)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSessionName(name: String){
|
||||
Log.v(tag, "mbuilder.setSession($name)")
|
||||
mbuilder.setSession(name)
|
||||
}
|
||||
|
||||
fun addHttpProxy(host: String, port: Int): Boolean{
|
||||
val proxyInfo = ProxyInfo.buildDirectProxy(host, port)
|
||||
Log.v(tag, "mbuilder.addHttpProxy($host, $port)")
|
||||
mbuilder.setHttpProxy(proxyInfo)
|
||||
/*
|
||||
* Checks if the VPN Permission is given.
|
||||
* If the permission is given, returns true
|
||||
* Requests permission and returns false if not.
|
||||
*/
|
||||
fun checkPermissions(): Boolean {
|
||||
// See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service
|
||||
// Call Prepare, if we get an Intent back, we dont have the VPN Permission
|
||||
// from the user. So we need to pass this to our main Activity and exit here.
|
||||
val intent = prepare(this)
|
||||
if (intent == null) {
|
||||
Log.e(tag, "VPN Permission Already Present")
|
||||
return true
|
||||
}
|
||||
Log.e(tag, "Requesting VPN Permission")
|
||||
return false
|
||||
}
|
||||
|
||||
fun setDomain(domain: String) {
|
||||
Log.v(tag, "mbuilder.setDomain($domain)")
|
||||
mbuilder.addSearchDomain(domain)
|
||||
}
|
||||
|
||||
fun turnOff() {
|
||||
Log.v(tag, "Try to disable tunnel")
|
||||
when(mProtocol){
|
||||
"wireguard" -> wgTurnOff(currentTunnelHandle)
|
||||
"openvpn" -> ovpnTurnOff()
|
||||
else -> {
|
||||
Log.e(tag, "No protocol")
|
||||
}
|
||||
}
|
||||
currentTunnelHandle = -1
|
||||
stopForeground(true)
|
||||
|
||||
fun turnOn(json: JSONObject?): Int {
|
||||
if (!checkPermissions()) {
|
||||
Log.e(tag, "turn on was called without no permissions present!")
|
||||
isUp = false
|
||||
stopSelf();
|
||||
return 0
|
||||
}
|
||||
Log.i(tag, "Permission okay")
|
||||
mConfig = json!!
|
||||
mProtocol = mConfig!!.getString("protocol")
|
||||
when (mProtocol) {
|
||||
"openvpn" -> startOpenVpn()
|
||||
"wireguard" -> startWireGuard()
|
||||
else -> {
|
||||
Log.e(tag, "No protocol")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
NotificationUtil.show(this) // Go foreground
|
||||
return 1
|
||||
}
|
||||
|
||||
fun establish(): ParcelFileDescriptor? {
|
||||
mbuilder.allowFamily(OsConstants.AF_INET)
|
||||
mbuilder.allowFamily(OsConstants.AF_INET6)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) mbuilder.setMetered(false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
|
||||
|
||||
return mbuilder.establish()
|
||||
}
|
||||
|
||||
fun setMtu(mtu: Int) {
|
||||
mbuilder.setMtu(mtu)
|
||||
}
|
||||
|
||||
fun addAddress(ip: String, len: Int) {
|
||||
Log.v(tag, "mbuilder.addAddress($ip, $len)")
|
||||
mbuilder.addAddress(ip, len)
|
||||
}
|
||||
|
||||
fun addRoute(ip: String, len: Int) {
|
||||
Log.v(tag, "mbuilder.addRoute($ip, $len)")
|
||||
mbuilder.addRoute(ip, len)
|
||||
}
|
||||
|
||||
fun addDNS(ip: String) {
|
||||
Log.v(tag, "mbuilder.addDnsServer($ip)")
|
||||
mbuilder.addDnsServer(ip)
|
||||
if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
mbuilder.addRoute(ip, 32)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSessionName(name: String) {
|
||||
Log.v(tag, "mbuilder.setSession($name)")
|
||||
mbuilder.setSession(name)
|
||||
}
|
||||
|
||||
fun addHttpProxy(host: String, port: Int): Boolean {
|
||||
val proxyInfo = ProxyInfo.buildDirectProxy(host, port)
|
||||
Log.v(tag, "mbuilder.addHttpProxy($host, $port)")
|
||||
mbuilder.setHttpProxy(proxyInfo)
|
||||
return true
|
||||
}
|
||||
|
||||
fun setDomain(domain: String) {
|
||||
Log.v(tag, "mbuilder.setDomain($domain)")
|
||||
mbuilder.addSearchDomain(domain)
|
||||
}
|
||||
|
||||
fun turnOff() {
|
||||
Log.v(tag, "Try to disable tunnel")
|
||||
when (mProtocol) {
|
||||
"wireguard" -> wgTurnOff(currentTunnelHandle)
|
||||
"openvpn" -> ovpnTurnOff()
|
||||
else -> {
|
||||
Log.e(tag, "No protocol")
|
||||
}
|
||||
}
|
||||
currentTunnelHandle = -1
|
||||
stopForeground(true)
|
||||
|
||||
isUp = false
|
||||
stopSelf();
|
||||
}
|
||||
|
||||
|
||||
private fun ovpnTurnOff() {
|
||||
mOpenVPNThreadv3?.stop()
|
||||
mOpenVPNThreadv3 = null
|
||||
Log.e(tag, "mOpenVPNThreadv3 stop!")
|
||||
}
|
||||
/**
|
||||
* Configures an Android VPN Service Tunnel
|
||||
* with a given Wireguard Config
|
||||
*/
|
||||
private fun setupBuilder(config: Config, builder: Builder) {
|
||||
// Setup Split tunnel
|
||||
for (excludedApplication in config.`interface`.excludedApplications)
|
||||
private fun ovpnTurnOff() {
|
||||
mOpenVPNThreadv3?.stop()
|
||||
mOpenVPNThreadv3 = null
|
||||
Log.e(tag, "mOpenVPNThreadv3 stop!")
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures an Android VPN Service Tunnel
|
||||
* with a given Wireguard Config
|
||||
*/
|
||||
private fun setupBuilder(config: Config, builder: Builder) {
|
||||
// Setup Split tunnel
|
||||
for (excludedApplication in config.`interface`.excludedApplications)
|
||||
builder.addDisallowedApplication(excludedApplication)
|
||||
|
||||
// Device IP
|
||||
for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask)
|
||||
// DNS
|
||||
for (addr in config.`interface`.dnsServers) builder.addDnsServer(addr.hostAddress)
|
||||
// Add All routes the VPN may route tos
|
||||
for (peer in config.peers) {
|
||||
for (addr in peer.allowedIps) {
|
||||
builder.addRoute(addr.address, addr.mask)
|
||||
}
|
||||
// Device IP
|
||||
for (addr in config.`interface`.addresses) builder.addAddress(addr.address, addr.mask)
|
||||
// DNS
|
||||
for (addr in config.`interface`.dnsServers) builder.addDnsServer(addr.hostAddress)
|
||||
// Add All routes the VPN may route tos
|
||||
for (peer in config.peers) {
|
||||
for (addr in peer.allowedIps) {
|
||||
builder.addRoute(addr.address, addr.mask)
|
||||
}
|
||||
builder.allowFamily(OsConstants.AF_INET)
|
||||
builder.allowFamily(OsConstants.AF_INET6)
|
||||
builder.setMtu(config.`interface`.mtu.orElse(1280))
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
|
||||
|
||||
builder.setBlocking(true)
|
||||
}
|
||||
builder.allowFamily(OsConstants.AF_INET)
|
||||
builder.allowFamily(OsConstants.AF_INET6)
|
||||
builder.setMtu(config.`interface`.mtu.orElse(1280))
|
||||
|
||||
/**
|
||||
* Gets config value for {key} from the Current
|
||||
* running Wireguard tunnel
|
||||
*/
|
||||
private fun getConfigValue(key: String): String? {
|
||||
if (!isUp) {
|
||||
return null
|
||||
}
|
||||
val config = wgGetConfig(currentTunnelHandle) ?: return null
|
||||
val lines = config.split("\n")
|
||||
for (line in lines) {
|
||||
val parts = line.split("=")
|
||||
val k = parts.first()
|
||||
val value = parts.last()
|
||||
if (key == k) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) builder.setMetered(false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) setUnderlyingNetworks(null)
|
||||
|
||||
builder.setBlocking(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets config value for {key} from the Current
|
||||
* running Wireguard tunnel
|
||||
*/
|
||||
private fun getConfigValue(key: String): String? {
|
||||
if (!isUp) {
|
||||
return null
|
||||
}
|
||||
val config = wgGetConfig(currentTunnelHandle) ?: return null
|
||||
val lines = config.split("\n")
|
||||
for (line in lines) {
|
||||
val parts = line.split("=")
|
||||
val k = parts.first()
|
||||
val value = parts.last()
|
||||
if (key == k) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseConfigData(data: String): Map<String, Map<String, String>> {
|
||||
val parseData = mutableMapOf<String, Map<String, String>>()
|
||||
var currentSection: Pair<String, MutableMap<String, String>>? = null
|
||||
data.lines().forEach { line ->
|
||||
if (line.isNotEmpty()) {
|
||||
if (line.startsWith('[')) {
|
||||
currentSection?.let {
|
||||
parseData.put(it.first, it.second)
|
||||
}
|
||||
currentSection = line.substring(1, line.indexOfLast { it == ']' }) to mutableMapOf()
|
||||
} else {
|
||||
val parameter = line.split("=", limit = 2)
|
||||
currentSection!!.second.put(parameter.first().trim(), parameter.last().trim())
|
||||
private fun parseConfigData(data: String): Map<String, Map<String, String>> {
|
||||
val parseData = mutableMapOf<String, Map<String, String>>()
|
||||
var currentSection: Pair<String, MutableMap<String, String>>? = null
|
||||
data.lines().forEach { line ->
|
||||
if (line.isNotEmpty()) {
|
||||
if (line.startsWith('[')) {
|
||||
currentSection?.let {
|
||||
parseData.put(it.first, it.second)
|
||||
}
|
||||
currentSection =
|
||||
line.substring(1, line.indexOfLast { it == ']' }) to mutableMapOf()
|
||||
} else {
|
||||
val parameter = line.split("=", limit = 2)
|
||||
currentSection!!.second.put(parameter.first().trim(), parameter.last().trim())
|
||||
}
|
||||
}
|
||||
currentSection?.let {
|
||||
parseData.put(it.first, it.second)
|
||||
}
|
||||
currentSection?.let {
|
||||
parseData.put(it.first, it.second)
|
||||
}
|
||||
return parseData
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Wireguard [Config] from a [json] string -
|
||||
* The [json] will be created in AndroidVpnProtocol.cpp
|
||||
*/
|
||||
private fun buildWireugardConfig(obj: JSONObject): Config {
|
||||
val confBuilder = Config.Builder()
|
||||
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
|
||||
val config = parseConfigData(wireguardConfigData.getString("config"))
|
||||
val peerBuilder = Peer.Builder()
|
||||
val peerConfig = config["Peer"]!!
|
||||
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
|
||||
peerConfig["PresharedKey"]?.let {
|
||||
peerBuilder.setPreSharedKey(Key.fromBase64(it))
|
||||
}
|
||||
val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
|
||||
if (allowedIPList.isEmpty()) {
|
||||
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internet)
|
||||
} else {
|
||||
allowedIPList.forEach {
|
||||
val network = InetNetwork.parse(it.trim())
|
||||
peerBuilder.addAllowedIp(network)
|
||||
}
|
||||
return parseData
|
||||
}
|
||||
peerBuilder.setEndpoint(InetEndpoint.parse(peerConfig["Endpoint"]))
|
||||
peerConfig["PersistentKeepalive"]?.let {
|
||||
peerBuilder.setPersistentKeepalive(it.toInt())
|
||||
}
|
||||
confBuilder.addPeer(peerBuilder.build())
|
||||
|
||||
val ifaceBuilder = Interface.Builder()
|
||||
val ifaceConfig = config["Interface"]!!
|
||||
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
|
||||
ifaceBuilder.addAddress(InetNetwork.parse(ifaceConfig["Address"]))
|
||||
ifaceConfig["DNS"]!!.split(",").forEach {
|
||||
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
|
||||
}
|
||||
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
|
||||
(0 until jExcludedApplication.length()).toList().forEach {
|
||||
val appName = jExcludedApplication.get(it).toString()
|
||||
ifaceBuilder.excludeApplication(appName)
|
||||
}*/
|
||||
confBuilder.setInterface(ifaceBuilder.build())
|
||||
|
||||
return confBuilder.build()
|
||||
}
|
||||
|
||||
fun getVpnConfig(): JSONObject {
|
||||
return mConfig!!
|
||||
}
|
||||
|
||||
private fun startOpenVpn() {
|
||||
mOpenVPNThreadv3 = OpenVPNThreadv3(this)
|
||||
Thread({
|
||||
mOpenVPNThreadv3?.run()
|
||||
}).start()
|
||||
}
|
||||
|
||||
private fun startWireGuard() {
|
||||
val wireguard_conf = buildWireugardConfig(mConfig!!)
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.e(tag, "Tunnel already up")
|
||||
// Turn the tunnel down because this might be a switch
|
||||
wgTurnOff(currentTunnelHandle)
|
||||
}
|
||||
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
|
||||
val builder = Builder()
|
||||
setupBuilder(wireguard_conf, builder)
|
||||
builder.setSession("avpn0")
|
||||
builder.establish().use { tun ->
|
||||
if (tun == null) return
|
||||
Log.i(tag, "Go backend " + wgVersion())
|
||||
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig)
|
||||
}
|
||||
if (currentTunnelHandle < 0) {
|
||||
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
|
||||
isUp = false
|
||||
return
|
||||
}
|
||||
protect(wgGetSocketV4(currentTunnelHandle))
|
||||
protect(wgGetSocketV6(currentTunnelHandle))
|
||||
isUp = true
|
||||
|
||||
// Store the config in case the service gets
|
||||
// asked boot vpn from the OS
|
||||
val prefs = Prefs.get(this)
|
||||
prefs.edit()
|
||||
.putString("lastConf", mConfig.toString())
|
||||
.apply()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun startService(c: Context) {
|
||||
c.applicationContext.startService(
|
||||
Intent(c.applicationContext, VPNService::class.java).apply {
|
||||
putExtra("startOnly", true)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a Wireguard [Config] from a [json] string -
|
||||
* The [json] will be created in AndroidVpnProtocol.cpp
|
||||
*/
|
||||
private fun buildWireugardConfig(obj: JSONObject): Config {
|
||||
val confBuilder = Config.Builder()
|
||||
val wireguardConfigData = obj.getJSONObject("wireguard_config_data")
|
||||
val config = parseConfigData(wireguardConfigData.getString("config"))
|
||||
val peerBuilder = Peer.Builder()
|
||||
val peerConfig = config["Peer"]!!
|
||||
peerBuilder.setPublicKey(Key.fromBase64(peerConfig["PublicKey"]))
|
||||
peerConfig["PresharedKey"]?.let {
|
||||
peerBuilder.setPreSharedKey(Key.fromBase64(it))
|
||||
}
|
||||
val allowedIPList = peerConfig["AllowedIPs"]?.split(",") ?: emptyList()
|
||||
if (allowedIPList.isEmpty()) {
|
||||
val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet.
|
||||
peerBuilder.addAllowedIp(internet)
|
||||
} else {
|
||||
allowedIPList.forEach {
|
||||
val network = InetNetwork.parse(it.trim())
|
||||
peerBuilder.addAllowedIp(network)
|
||||
}
|
||||
}
|
||||
peerBuilder.setEndpoint(InetEndpoint.parse(peerConfig["Endpoint"]))
|
||||
peerConfig["PersistentKeepalive"]?.let {
|
||||
peerBuilder.setPersistentKeepalive(it.toInt())
|
||||
}
|
||||
confBuilder.addPeer(peerBuilder.build())
|
||||
@JvmStatic
|
||||
private external fun wgGetConfig(handle: Int): String?
|
||||
|
||||
val ifaceBuilder = Interface.Builder()
|
||||
val ifaceConfig = config["Interface"]!!
|
||||
ifaceBuilder.parsePrivateKey(ifaceConfig["PrivateKey"])
|
||||
ifaceBuilder.addAddress(InetNetwork.parse(ifaceConfig["Address"]))
|
||||
ifaceConfig["DNS"]!!.split(",").forEach {
|
||||
ifaceBuilder.addDnsServer(InetNetwork.parse(it.trim()).address)
|
||||
}
|
||||
/*val jExcludedApplication = obj.getJSONArray("excludedApps")
|
||||
(0 until jExcludedApplication.length()).toList().forEach {
|
||||
val appName = jExcludedApplication.get(it).toString()
|
||||
ifaceBuilder.excludeApplication(appName)
|
||||
}*/
|
||||
confBuilder.setInterface(ifaceBuilder.build())
|
||||
@JvmStatic
|
||||
private external fun wgGetSocketV4(handle: Int): Int
|
||||
|
||||
return confBuilder.build()
|
||||
}
|
||||
@JvmStatic
|
||||
private external fun wgGetSocketV6(handle: Int): Int
|
||||
|
||||
fun getVpnConfig(): JSONObject {
|
||||
return mConfig!!
|
||||
}
|
||||
@JvmStatic
|
||||
private external fun wgTurnOff(handle: Int)
|
||||
|
||||
private fun startOpenVpn() {
|
||||
mOpenVPNThreadv3 = OpenVPNThreadv3 (this)
|
||||
Thread ({
|
||||
mOpenVPNThreadv3?.run()
|
||||
}).start()
|
||||
}
|
||||
@JvmStatic
|
||||
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
|
||||
|
||||
private fun startWireGuard(){
|
||||
val wireguard_conf = buildWireugardConfig(mConfig!!)
|
||||
if (currentTunnelHandle != -1) {
|
||||
Log.e(tag, "Tunnel already up")
|
||||
// Turn the tunnel down because this might be a switch
|
||||
wgTurnOff(currentTunnelHandle)
|
||||
}
|
||||
val wgConfig: String = wireguard_conf!!.toWgUserspaceString()
|
||||
val builder = Builder()
|
||||
setupBuilder(wireguard_conf, builder)
|
||||
builder.setSession("avpn0")
|
||||
builder.establish().use { tun ->
|
||||
if (tun == null)return
|
||||
Log.i(tag, "Go backend " + wgVersion())
|
||||
currentTunnelHandle = wgTurnOn("avpn0", tun.detachFd(), wgConfig)
|
||||
}
|
||||
if (currentTunnelHandle < 0) {
|
||||
Log.e(tag, "Activation Error Code -> $currentTunnelHandle")
|
||||
isUp = false
|
||||
return
|
||||
}
|
||||
protect(wgGetSocketV4(currentTunnelHandle))
|
||||
protect(wgGetSocketV6(currentTunnelHandle))
|
||||
isUp = true
|
||||
|
||||
// Store the config in case the service gets
|
||||
// asked boot vpn from the OS
|
||||
val prefs = Prefs.get(this)
|
||||
prefs.edit()
|
||||
.putString("lastConf", mConfig.toString())
|
||||
.apply()
|
||||
|
||||
NotificationUtil.show(this) // Go foreground
|
||||
}
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun startService(c: Context) {
|
||||
c.applicationContext.startService(
|
||||
Intent(c.applicationContext, VPNService::class.java).apply {
|
||||
putExtra("startOnly", true)
|
||||
})
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
private external fun wgGetConfig(handle: Int): String?
|
||||
@JvmStatic
|
||||
private external fun wgGetSocketV4(handle: Int): Int
|
||||
@JvmStatic
|
||||
private external fun wgGetSocketV6(handle: Int): Int
|
||||
@JvmStatic
|
||||
private external fun wgTurnOff(handle: Int)
|
||||
@JvmStatic
|
||||
private external fun wgTurnOn(ifName: String, tunFd: Int, settings: String): Int
|
||||
@JvmStatic
|
||||
private external fun wgVersion(): String?
|
||||
}
|
||||
}
|
||||
@JvmStatic
|
||||
private external fun wgVersion(): String?
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
QT += widgets core gui network xml remoteobjects quick
|
||||
QT += widgets core gui network xml remoteobjects quick svg
|
||||
|
||||
TARGET = AmneziaVPN
|
||||
TEMPLATE = app
|
||||
@@ -15,7 +15,7 @@ include("3rd/QtSsh/src/ssh/qssh.pri")
|
||||
include("3rd/QtSsh/src/botan/botan.pri")
|
||||
!android:!ios:include("3rd/SingleApplication/singleapplication.pri")
|
||||
include ("3rd/SortFilterProxyModel/SortFilterProxyModel.pri")
|
||||
include("3rd/QZXing/src/QZXing-components.pri")
|
||||
include("3rd/qzxing/src/QZXing-components.pri")
|
||||
|
||||
INCLUDEPATH += $$PWD/3rd/OpenSSL/include
|
||||
DEPENDPATH += $$PWD/3rd/OpenSSL/include
|
||||
@@ -31,15 +31,14 @@ HEADERS += \
|
||||
containers/containers_defs.h \
|
||||
core/defs.h \
|
||||
core/errorstrings.h \
|
||||
core/ipcclient.h \
|
||||
configurators/openvpn_configurator.h \
|
||||
core/privileged_process.h \
|
||||
core/scripts_registry.h \
|
||||
core/server_defs.h \
|
||||
core/servercontroller.h \
|
||||
debug.h \
|
||||
defines.h \
|
||||
managementserver.h \
|
||||
platforms/linux/leakdetector.h \
|
||||
protocols/protocols_defs.h \
|
||||
settings.h \
|
||||
ui/notificationhandler.h \
|
||||
@@ -88,15 +87,14 @@ SOURCES += \
|
||||
configurators/wireguard_configurator.cpp \
|
||||
containers/containers_defs.cpp \
|
||||
core/errorstrings.cpp \
|
||||
core/ipcclient.cpp \
|
||||
configurators/openvpn_configurator.cpp \
|
||||
core/privileged_process.cpp \
|
||||
core/scripts_registry.cpp \
|
||||
core/server_defs.cpp \
|
||||
core/servercontroller.cpp \
|
||||
debug.cpp \
|
||||
main.cpp \
|
||||
managementserver.cpp \
|
||||
platforms/linux/leakdetector.cpp \
|
||||
protocols/protocols_defs.cpp \
|
||||
settings.cpp \
|
||||
ui/notificationhandler.cpp \
|
||||
@@ -195,13 +193,24 @@ macx {
|
||||
linux:!android {
|
||||
DEFINES += MVPN_LINUX
|
||||
|
||||
HEADERS += \
|
||||
platforms/linux/linuxsystemtraynotificationhandler.h \
|
||||
|
||||
SOURCES += \
|
||||
platforms/linux/linuxsystemtraynotificationhandler.cpp \
|
||||
|
||||
LIBS += /usr/lib/x86_64-linux-gnu/libcrypto.a
|
||||
LIBS += /usr/lib/x86_64-linux-gnu/libssl.a
|
||||
|
||||
INCLUDEPATH += $$PWD/platforms/linux
|
||||
}
|
||||
|
||||
win32|macx|linux:!android {
|
||||
DEFINES += AMNEZIA_DESKTOP
|
||||
|
||||
HEADERS += \
|
||||
core/ipcclient.h \
|
||||
core/privileged_process.h \
|
||||
ui/systemtray_notificationhandler.h \
|
||||
protocols/openvpnprotocol.h \
|
||||
protocols/openvpnovercloakprotocol.h \
|
||||
@@ -209,11 +218,16 @@ win32|macx|linux:!android {
|
||||
protocols/wireguardprotocol.h \
|
||||
|
||||
SOURCES += \
|
||||
core/ipcclient.cpp \
|
||||
core/privileged_process.cpp \
|
||||
ui/systemtray_notificationhandler.cpp \
|
||||
protocols/openvpnprotocol.cpp \
|
||||
protocols/openvpnovercloakprotocol.cpp \
|
||||
protocols/shadowsocksvpnprotocol.cpp \
|
||||
protocols/wireguardprotocol.cpp \
|
||||
|
||||
REPC_REPLICA += ../ipc/ipc_interface.rep
|
||||
REPC_REPLICA += ../ipc/ipc_process_interface.rep
|
||||
}
|
||||
|
||||
android {
|
||||
@@ -364,6 +378,4 @@ ios {
|
||||
}
|
||||
|
||||
|
||||
REPC_REPLICA += ../ipc/ipc_interface.rep
|
||||
!ios: REPC_REPLICA += ../ipc/ipc_process_interface.rep
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <QJsonDocument>
|
||||
|
||||
#include "containers/containers_defs.h"
|
||||
#include "utils.h"
|
||||
|
||||
Settings &VpnConfigurator::m_settings()
|
||||
{
|
||||
@@ -41,24 +42,59 @@ QString VpnConfigurator::genVpnProtocolConfig(const ServerCredentials &credentia
|
||||
}
|
||||
}
|
||||
|
||||
QString VpnConfigurator::processConfigWithLocalSettings(DockerContainer container, Proto proto, QString config)
|
||||
QPair<QString, QString> VpnConfigurator::getDnsForConfig(int serverIndex)
|
||||
{
|
||||
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
|
||||
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
|
||||
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) {
|
||||
return OpenVpnConfigurator::processConfigWithLocalSettings(config);
|
||||
config = OpenVpnConfigurator::processConfigWithLocalSettings(config);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
QString VpnConfigurator::processConfigWithExportSettings(DockerContainer container, Proto proto, QString config)
|
||||
QString &VpnConfigurator::processConfigWithExportSettings(int serverIndex, DockerContainer container,
|
||||
Proto proto, QString &config)
|
||||
{
|
||||
config.replace("$PRIMARY_DNS", m_settings().primaryDns());
|
||||
config.replace("$SECONDARY_DNS", m_settings().secondaryDns());
|
||||
processConfigWithDnsSettings(serverIndex, container, proto, config);
|
||||
|
||||
if (proto == Proto::OpenVpn) {
|
||||
return OpenVpnConfigurator::processConfigWithExportSettings(config);
|
||||
config = OpenVpnConfigurator::processConfigWithExportSettings(config);
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,11 @@ public:
|
||||
static QString genVpnProtocolConfig(const ServerCredentials &credentials, DockerContainer container,
|
||||
const QJsonObject &containerConfig, Proto proto, ErrorCode *errorCode = nullptr);
|
||||
|
||||
static QString processConfigWithLocalSettings(DockerContainer container, Proto proto, QString config);
|
||||
static QString processConfigWithExportSettings(DockerContainer container, Proto proto, QString config);
|
||||
static QPair<QString, QString> getDnsForConfig(int serverIndex);
|
||||
static QString &processConfigWithDnsSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
|
||||
static QString &processConfigWithLocalSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
static QString &processConfigWithExportSettings(int serverIndex, DockerContainer container, Proto proto, QString &config);
|
||||
|
||||
// workaround for containers which is not support normal configaration
|
||||
static void updateContainerConfigAfterInstallation(DockerContainer container,
|
||||
|
||||
@@ -136,7 +136,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
|
||||
}
|
||||
}
|
||||
|
||||
bool ContainerProps::isWorkingOnPlatform(DockerContainer c)
|
||||
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
|
||||
{
|
||||
#ifdef Q_OS_WINDOWS
|
||||
return true;
|
||||
@@ -148,7 +148,11 @@ bool ContainerProps::isWorkingOnPlatform(DockerContainer c)
|
||||
default: return false;
|
||||
}
|
||||
#elif defined (Q_OS_MAC)
|
||||
return false;
|
||||
switch (c) {
|
||||
case DockerContainer::WireGuard: return false;
|
||||
case DockerContainer::Ipsec: return false;
|
||||
default: return true;
|
||||
}
|
||||
|
||||
#elif defined (Q_OS_ANDROID)
|
||||
switch (c) {
|
||||
@@ -158,7 +162,7 @@ bool ContainerProps::isWorkingOnPlatform(DockerContainer c)
|
||||
}
|
||||
|
||||
#elif defined (Q_OS_LINUX)
|
||||
return false;
|
||||
return true;
|
||||
|
||||
#else
|
||||
return false;
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
// it may be changed fot future containers :)
|
||||
Q_INVOKABLE static Proto defaultProtocol(DockerContainer c);
|
||||
|
||||
Q_INVOKABLE static bool isWorkingOnPlatform(DockerContainer c);
|
||||
Q_INVOKABLE static bool isSupportedByCurrentPlatform(DockerContainer c);
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ bool IpcClient::init(IpcClient *instance)
|
||||
|
||||
QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
{
|
||||
#ifndef Q_OS_IOS
|
||||
if (! Instance()->m_ipcClient || ! Instance()->m_ipcClient->isReplicaValid()) {
|
||||
qWarning() << "IpcClient::createPrivilegedProcess : IpcClient IpcClient replica is not valid";
|
||||
return nullptr;
|
||||
@@ -107,9 +106,6 @@ QSharedPointer<PrivilegedProcess> IpcClient::CreatePrivilegedProcess()
|
||||
|
||||
auto proccessReplica = QSharedPointer<PrivilegedProcess>(pd->ipcProcess);
|
||||
return proccessReplica;
|
||||
#else
|
||||
return QSharedPointer<PrivilegedProcess>();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "privileged_process.h"
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
PrivilegedProcess::PrivilegedProcess() :
|
||||
IpcProcessInterfaceReplica()
|
||||
{
|
||||
@@ -26,4 +25,3 @@ void PrivilegedProcess::waitForFinished(int msecs)
|
||||
|
||||
loop->exec();
|
||||
}
|
||||
#endif // Q_OS_IOS
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
#include "rep_ipc_process_interface_replica.h"
|
||||
// This class is dangerous - instance of this class casted from base class,
|
||||
// so it support only functions
|
||||
@@ -20,11 +19,6 @@ public:
|
||||
|
||||
};
|
||||
|
||||
#else // defined Q_OS_IOS
|
||||
class IpcProcessInterfaceReplica {};
|
||||
class PrivilegedProcess {};
|
||||
#endif // Q_OS_IOS
|
||||
|
||||
#endif // PRIVILEGED_PROCESS_H
|
||||
|
||||
|
||||
|
||||
@@ -79,6 +79,8 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
||||
}
|
||||
|
||||
qDebug().noquote() << "EXEC" << lineToExec;
|
||||
Debug::appendSshLog("Run command:" + lineToExec);
|
||||
|
||||
QSharedPointer<SshRemoteProcess> proc = client->createRemoteProcess(lineToExec.toUtf8());
|
||||
|
||||
if (!proc) {
|
||||
@@ -101,7 +103,9 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
||||
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardOutput, &wait, [proc, cbReadStdOut](){
|
||||
QString s = proc->readAllStandardOutput();
|
||||
|
||||
if (s != "." && !s.isEmpty()) {
|
||||
Debug::appendSshLog("Output: " + s);
|
||||
qDebug().noquote() << "stdout" << s;
|
||||
}
|
||||
if (cbReadStdOut) cbReadStdOut(s, proc);
|
||||
@@ -110,6 +114,7 @@ ErrorCode ServerController::runScript(const ServerCredentials &credentials, QStr
|
||||
QObject::connect(proc.data(), &SshRemoteProcess::readyReadStandardError, &wait, [proc, cbReadStdErr](){
|
||||
QString s = proc->readAllStandardError();
|
||||
if (s != "." && !s.isEmpty()) {
|
||||
Debug::appendSshLog("Output: " + s);
|
||||
qDebug().noquote() << "stderr" << s;
|
||||
}
|
||||
if (cbReadStdErr) cbReadStdErr(s, proc);
|
||||
@@ -135,6 +140,7 @@ ErrorCode ServerController::runContainerScript(const ServerCredentials &credenti
|
||||
const std::function<void (const QString &, QSharedPointer<QSsh::SshRemoteProcess>)> &cbReadStdErr)
|
||||
{
|
||||
QString fileName = "/opt/amnezia/" + Utils::getRandomString(16) + ".sh";
|
||||
Debug::appendSshLog("Run container script for " + ContainerProps::containerToString(container) + ":\n" + script);
|
||||
|
||||
ErrorCode e = uploadTextFileToContainer(container, credentials, script, fileName);
|
||||
if (e) return e;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <QObject>
|
||||
#include "sshconnection.h"
|
||||
#include "sshremoteprocess.h"
|
||||
#include "debug.h"
|
||||
#include "defs.h"
|
||||
#include "settings.h"
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDesktopServices>
|
||||
#include <QDir>
|
||||
@@ -10,9 +11,13 @@
|
||||
#include "defines.h"
|
||||
#include "utils.h"
|
||||
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
#include <core/ipcclient.h>
|
||||
#endif
|
||||
|
||||
QFile Debug::m_file;
|
||||
QTextStream Debug::m_textStream;
|
||||
QString Debug::m_logFileName;
|
||||
QString Debug::m_logFileName = QString("%1.log").arg(APPLICATION_NAME);
|
||||
|
||||
void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& msg)
|
||||
{
|
||||
@@ -26,10 +31,30 @@ void debugMessageHandler(QtMsgType type, const QMessageLogContext& context, cons
|
||||
}
|
||||
|
||||
Debug::m_textStream << qFormatLogMessage(type, context, msg) << endl << flush;
|
||||
Debug::appendAllLog(qFormatLogMessage(type, context, msg));
|
||||
|
||||
std::cout << qFormatLogMessage(type, context, msg).toStdString() << std::endl << std::flush;
|
||||
}
|
||||
|
||||
Debug &Debug::Instance()
|
||||
{
|
||||
static Debug s;
|
||||
return s;
|
||||
}
|
||||
|
||||
void Debug::appendSshLog(const QString &log)
|
||||
{
|
||||
QString dt = QDateTime::currentDateTime().toString();
|
||||
Instance().m_sshLog.append(dt + ": " + log + "\n");
|
||||
emit Instance().sshLogChanged(Instance().sshLog());
|
||||
}
|
||||
|
||||
void Debug::appendAllLog(const QString &log)
|
||||
{
|
||||
Instance().m_allLog.append(log + "\n");
|
||||
emit Instance().allLogChanged(Instance().allLog());
|
||||
}
|
||||
|
||||
bool Debug::init()
|
||||
{
|
||||
qSetMessagePattern("%{time yyyy-MM-dd hh:mm:ss} %{type} %{message}");
|
||||
@@ -40,11 +65,8 @@ bool Debug::init()
|
||||
return false;
|
||||
}
|
||||
|
||||
m_logFileName = QString("%1.log").arg(APPLICATION_NAME);
|
||||
|
||||
|
||||
m_file.setFileName(appDir.filePath(m_logFileName));
|
||||
if (!m_file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
if (!m_file.open(QIODevice::Append)) {
|
||||
qWarning() << "Cannot open log file:" << m_logFileName;
|
||||
return false;
|
||||
}
|
||||
@@ -63,6 +85,20 @@ QString Debug::userLogsDir()
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/log";
|
||||
}
|
||||
|
||||
QString Debug::userLogsFilePath()
|
||||
{
|
||||
return userLogsDir() + QDir::separator() + m_logFileName;
|
||||
}
|
||||
|
||||
QString Debug::getLogFile()
|
||||
{
|
||||
m_file.flush();
|
||||
QFile file(userLogsFilePath());
|
||||
|
||||
file.open(QIODevice::ReadOnly);
|
||||
return file.readAll();
|
||||
}
|
||||
|
||||
bool Debug::openLogsFolder()
|
||||
{
|
||||
QString path = userLogsDir();
|
||||
@@ -88,3 +124,50 @@ QString Debug::appLogFileNamePath()
|
||||
{
|
||||
return m_file.fileName();
|
||||
}
|
||||
|
||||
void Debug::clearLogs()
|
||||
{
|
||||
bool isLogActive = m_file.isOpen();
|
||||
m_file.close();
|
||||
|
||||
QFile file(userLogsFilePath());
|
||||
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
|
||||
file.resize(0);
|
||||
file.close();
|
||||
|
||||
if (isLogActive) {
|
||||
init();
|
||||
}
|
||||
}
|
||||
|
||||
void Debug::clearServiceLogs()
|
||||
{
|
||||
#ifdef AMNEZIA_DESKTOP
|
||||
IpcClient *m_IpcClient = new IpcClient;
|
||||
|
||||
if (!m_IpcClient->isSocketConnected()) {
|
||||
if (!IpcClient::init(m_IpcClient)) {
|
||||
qWarning() << "Error occured when init IPC client";
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_IpcClient->Interface()) {
|
||||
m_IpcClient->Interface()->setLogsEnabled(false);
|
||||
m_IpcClient->Interface()->cleanUp();
|
||||
}
|
||||
else {
|
||||
qWarning() << "Error occured cleaning up service logs";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void Debug::cleanUp()
|
||||
{
|
||||
clearLogs();
|
||||
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
|
||||
dir.removeRecursively();
|
||||
|
||||
clearServiceLogs();
|
||||
}
|
||||
|
||||
@@ -7,15 +7,37 @@
|
||||
#include <QString>
|
||||
#include <QTextStream>
|
||||
|
||||
class Debug
|
||||
#include "ui/property_helper.h"
|
||||
|
||||
class Debug : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
AUTO_PROPERTY(QString, sshLog)
|
||||
AUTO_PROPERTY(QString, allLog)
|
||||
|
||||
public:
|
||||
static Debug& Instance();
|
||||
|
||||
static void appendSshLog(const QString &log);
|
||||
static void appendAllLog(const QString &log);
|
||||
|
||||
|
||||
static bool init();
|
||||
static bool openLogsFolder();
|
||||
static bool openServiceLogsFolder();
|
||||
static QString appLogFileNamePath();
|
||||
static void clearLogs();
|
||||
static void clearServiceLogs();
|
||||
static void cleanUp();
|
||||
|
||||
static QString userLogsFilePath();
|
||||
static QString getLogFile();
|
||||
|
||||
private:
|
||||
Debug() {}
|
||||
Debug(Debug const &) = delete;
|
||||
Debug& operator= (Debug const&) = delete;
|
||||
|
||||
static QString userLogsDir();
|
||||
|
||||
static QFile m_file;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#define APPLICATION_NAME "AmneziaVPN"
|
||||
#define SERVICE_NAME "AmneziaVPN-service"
|
||||
#define ORGANIZATION_NAME "AmneziaVPN.ORG"
|
||||
#define APP_MAJOR_VERSION "2.0.7"
|
||||
#define APP_VERSION "2.0.7.0"
|
||||
#define APP_MAJOR_VERSION "2.0.8"
|
||||
#define APP_VERSION "2.0.8.0"
|
||||
|
||||
#endif // DEFINES_H
|
||||
|
||||
|
Before Width: | Height: | Size: 324 B |
|
Before Width: | Height: | Size: 690 B |
|
Before Width: | Height: | Size: 454 B |
1
client/images/svg/close_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12 19 6.41z"/></svg>
|
||||
|
After Width: | Height: | Size: 268 B |
1
client/images/svg/control_point_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
||||
|
After Width: | Height: | Size: 317 B |
1
client/images/svg/delete_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z"/></svg>
|
||||
|
After Width: | Height: | Size: 252 B |
1
client/images/svg/density_small_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><rect height="2" width="18" x="3" y="2"/><rect height="2" width="18" x="3" y="20"/><rect height="2" width="18" x="3" y="14"/><rect height="2" width="18" x="3" y="8"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 371 B |
1
client/images/svg/done_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z"/></svg>
|
||||
|
After Width: | Height: | Size: 209 B |
1
client/images/svg/format_list_bulleted_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M4 10.5c-.83 0-1.5.67-1.5 1.5s.67 1.5 1.5 1.5 1.5-.67 1.5-1.5-.67-1.5-1.5-1.5zm0-6c-.83 0-1.5.67-1.5 1.5S3.17 7.5 4 7.5 5.5 6.83 5.5 6 4.83 4.5 4 4.5zm0 12c-.83 0-1.5.68-1.5 1.5s.68 1.5 1.5 1.5 1.5-.68 1.5-1.5-.67-1.5-1.5-1.5zM7 19h14v-2H7v2zm0-6h14v-2H7v2zm0-8v2h14V5H7z"/></svg>
|
||||
|
After Width: | Height: | Size: 430 B |
1
client/images/svg/gpp_good_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M12,2L4,5v6.09c0,5.05,3.41,9.76,8,10.91c4.59-1.15,8-5.86,8-10.91V5L12,2z M18,11.09c0,4-2.55,7.7-6,8.83 c-3.45-1.13-6-4.82-6-8.83V6.31l6-2.12l6,2.12V11.09z M8.82,10.59L7.4,12l3.54,3.54l5.66-5.66l-1.41-1.41l-4.24,4.24L8.82,10.59z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 434 B |
1
client/images/svg/gpp_maybe_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><g><path d="M12,2L4,5v6.09c0,5.05,3.41,9.76,8,10.91c4.59-1.15,8-5.86,8-10.91V5L12,2z M18,11.09c0,4-2.55,7.7-6,8.83 c-3.45-1.13-6-4.82-6-8.83v-4.7l6-2.25l6,2.25V11.09z"/><rect height="2" width="2" x="11" y="14"/><rect height="5" width="2" x="11" y="7"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 451 B |
1
client/images/svg/logout_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><path d="M0,0h24v24H0V0z" fill="none"/></g><g><path d="M17,8l-1.41,1.41L17.17,11H9v2h8.17l-1.58,1.58L17,16l4-4L17,8z M5,5h7V3H5C3.9,3,3,3.9,3,5v14c0,1.1,0.9,2,2,2h7v-2H5V5z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 324 B |
1
client/images/svg/miscellaneous_services_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><g><rect fill="none" height="24" width="24"/></g><g><g><path d="M14.17,13.71l1.4-2.42c0.09-0.15,0.05-0.34-0.08-0.45l-1.48-1.16c0.03-0.22,0.05-0.45,0.05-0.68s-0.02-0.46-0.05-0.69 l1.48-1.16c0.13-0.11,0.17-0.3,0.08-0.45l-1.4-2.42c-0.09-0.15-0.27-0.21-0.43-0.15L12,4.83c-0.36-0.28-0.75-0.51-1.18-0.69 l-0.26-1.85C10.53,2.13,10.38,2,10.21,2h-2.8C7.24,2,7.09,2.13,7.06,2.3L6.8,4.15C6.38,4.33,5.98,4.56,5.62,4.84l-1.74-0.7 c-0.16-0.06-0.34,0-0.43,0.15l-1.4,2.42C1.96,6.86,2,7.05,2.13,7.16l1.48,1.16C3.58,8.54,3.56,8.77,3.56,9s0.02,0.46,0.05,0.69 l-1.48,1.16C2,10.96,1.96,11.15,2.05,11.3l1.4,2.42c0.09,0.15,0.27,0.21,0.43,0.15l1.74-0.7c0.36,0.28,0.75,0.51,1.18,0.69 l0.26,1.85C7.09,15.87,7.24,16,7.41,16h2.8c0.17,0,0.32-0.13,0.35-0.3l0.26-1.85c0.42-0.18,0.82-0.41,1.18-0.69l1.74,0.7 C13.9,13.92,14.08,13.86,14.17,13.71z M8.81,11c-1.1,0-2-0.9-2-2c0-1.1,0.9-2,2-2s2,0.9,2,2C10.81,10.1,9.91,11,8.81,11z"/><path d="M21.92,18.67l-0.96-0.74c0.02-0.14,0.04-0.29,0.04-0.44c0-0.15-0.01-0.3-0.04-0.44l0.95-0.74 c0.08-0.07,0.11-0.19,0.05-0.29l-0.9-1.55c-0.05-0.1-0.17-0.13-0.28-0.1l-1.11,0.45c-0.23-0.18-0.48-0.33-0.76-0.44l-0.17-1.18 C18.73,13.08,18.63,13,18.53,13h-1.79c-0.11,0-0.21,0.08-0.22,0.19l-0.17,1.18c-0.27,0.12-0.53,0.26-0.76,0.44l-1.11-0.45 c-0.1-0.04-0.22,0-0.28,0.1l-0.9,1.55c-0.05,0.1-0.04,0.22,0.05,0.29l0.95,0.74c-0.02,0.14-0.03,0.29-0.03,0.44 c0,0.15,0.01,0.3,0.03,0.44l-0.95,0.74c-0.08,0.07-0.11,0.19-0.05,0.29l0.9,1.55c0.05,0.1,0.17,0.13,0.28,0.1l1.11-0.45 c0.23,0.18,0.48,0.33,0.76,0.44l0.17,1.18c0.02,0.11,0.11,0.19,0.22,0.19h1.79c0.11,0,0.21-0.08,0.22-0.19l0.17-1.18 c0.27-0.12,0.53-0.26,0.75-0.44l1.12,0.45c0.1,0.04,0.22,0,0.28-0.1l0.9-1.55C22.03,18.86,22,18.74,21.92,18.67z M17.63,18.83 c-0.74,0-1.35-0.6-1.35-1.35s0.6-1.35,1.35-1.35s1.35,0.6,1.35,1.35S18.37,18.83,17.63,18.83z"/></g></g></svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
1
client/images/svg/refresh_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
|
||||
|
After Width: | Height: | Size: 361 B |
1
client/images/svg/settings_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19.43 12.98c.04-.32.07-.64.07-.98 0-.34-.03-.66-.07-.98l2.11-1.65c.19-.15.24-.42.12-.64l-2-3.46c-.09-.16-.26-.25-.44-.25-.06 0-.12.01-.17.03l-2.49 1c-.52-.4-1.08-.73-1.69-.98l-.38-2.65C14.46 2.18 14.25 2 14 2h-4c-.25 0-.46.18-.49.42l-.38 2.65c-.61.25-1.17.59-1.69.98l-2.49-1c-.06-.02-.12-.03-.18-.03-.17 0-.34.09-.43.25l-2 3.46c-.13.22-.07.49.12.64l2.11 1.65c-.04.32-.07.65-.07.98 0 .33.03.66.07.98l-2.11 1.65c-.19.15-.24.42-.12.64l2 3.46c.09.16.26.25.44.25.06 0 .12-.01.17-.03l2.49-1c.52.4 1.08.73 1.69.98l.38 2.65c.03.24.24.42.49.42h4c.25 0 .46-.18.49-.42l.38-2.65c.61-.25 1.17-.59 1.69-.98l2.49 1c.06.02.12.03.18.03.17 0 .34-.09.43-.25l2-3.46c.12-.22.07-.49-.12-.64l-2.11-1.65zm-1.98-1.71c.04.31.05.52.05.73 0 .21-.02.43-.05.73l-.14 1.13.89.7 1.08.84-.7 1.21-1.27-.51-1.04-.42-.9.68c-.43.32-.84.56-1.25.73l-1.06.43-.16 1.13-.2 1.35h-1.4l-.19-1.35-.16-1.13-1.06-.43c-.43-.18-.83-.41-1.23-.71l-.91-.7-1.06.43-1.27.51-.7-1.21 1.08-.84.89-.7-.14-1.13c-.03-.31-.05-.54-.05-.74s.02-.43.05-.73l.14-1.13-.89-.7-1.08-.84.7-1.21 1.27.51 1.04.42.9-.68c.43-.32.84-.56 1.25-.73l1.06-.43.16-1.13.2-1.35h1.39l.19 1.35.16 1.13 1.06.43c.43.18.83.41 1.23.71l.91.7 1.06-.43 1.27-.51.7 1.21-1.07.85-.89.7.14 1.13zM12 8c-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4-1.79-4-4-4zm0 6c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
1
client/images/svg/settings_suggest_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><rect fill="none" height="24" width="24"/><path d="M10,13c0.55,0,1,0.45,1,1s-0.45,1-1,1s-1-0.45-1-1S9.45,13,10,13 M10,11c-1.66,0-3,1.34-3,3s1.34,3,3,3s3-1.34,3-3 S11.66,11,10,11L10,11z M18.5,9l1.09-2.41L22,5.5l-2.41-1.09L18.5,2l-1.09,2.41L15,5.5l2.41,1.09L18.5,9z M21.28,12.72L20.5,11 l-0.78,1.72L18,13.5l1.72,0.78L20.5,16l0.78-1.72L23,13.5L21.28,12.72z M16.25,14c0-0.12,0-0.25-0.01-0.37l1.94-1.47l-2.5-4.33 l-2.24,0.94c-0.2-0.13-0.42-0.26-0.64-0.37L12.5,6h-5L7.2,8.41C6.98,8.52,6.77,8.65,6.56,8.78L4.32,7.83l-2.5,4.33l1.94,1.47 C3.75,13.75,3.75,13.88,3.75,14s0,0.25,0.01,0.37l-1.94,1.47l2.5,4.33l2.24-0.94c0.2,0.13,0.42,0.26,0.64,0.37L7.5,22h5l0.3-2.41 c0.22-0.11,0.43-0.23,0.64-0.37l2.24,0.94l2.5-4.33l-1.94-1.47C16.25,14.25,16.25,14.12,16.25,14z M14.83,17.64l-1.73-0.73 c-0.56,0.6-1.3,1.04-2.13,1.23L10.73,20H9.27l-0.23-1.86c-0.83-0.19-1.57-0.63-2.13-1.23l-1.73,0.73l-0.73-1.27l1.49-1.13 c-0.12-0.39-0.18-0.8-0.18-1.23c0-0.43,0.06-0.84,0.18-1.23l-1.49-1.13l0.73-1.27l1.73,0.73c0.56-0.6,1.3-1.04,2.13-1.23L9.27,8 h1.47l0.23,1.86c0.83,0.19,1.57,0.63,2.13,1.23l1.73-0.73l0.73,1.27l-1.49,1.13c0.12,0.39,0.18,0.8,0.18,1.23 c0,0.43-0.06,0.84-0.18,1.23l1.49,1.13L14.83,17.64z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
client/images/svg/share_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92c0-1.61-1.31-2.92-2.92-2.92zM18 4c.55 0 1 .45 1 1s-.45 1-1 1-1-.45-1-1 .45-1 1-1zM6 13c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1zm12 7.02c-.55 0-1-.45-1-1s.45-1 1-1 1 .45 1 1-.45 1-1 1z"/></svg>
|
||||
|
After Width: | Height: | Size: 679 B |
1
client/images/svg/vpn_key_black_24dp.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M22 19h-6v-4h-2.68c-1.14 2.42-3.6 4-6.32 4-3.86 0-7-3.14-7-7s3.14-7 7-7c2.72 0 5.17 1.58 6.32 4H24v6h-2v4zm-4-2h2v-4h2v-2H11.94l-.23-.67C11.01 8.34 9.11 7 7 7c-2.76 0-5 2.24-5 5s2.24 5 5 5c2.11 0 4.01-1.34 4.71-3.33l.23-.67H18v4zM7 15c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3zm0-4c-.55 0-1 .45-1 1s.45 1 1 1 1-.45 1-1-.45-1-1-1z"/></svg>
|
||||
|
After Width: | Height: | Size: 498 B |
@@ -86,15 +86,15 @@ int main(int argc, char *argv[])
|
||||
QApplication app(argc, argv);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
NativeHelpers::registerApplicationInstance(&app);
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
AllowSetForegroundWindow(0);
|
||||
#endif
|
||||
|
||||
#if defined(Q_OS_ANDROID)
|
||||
NativeHelpers::registerApplicationInstance(&app);
|
||||
#endif
|
||||
|
||||
loadTranslator();
|
||||
|
||||
QFontDatabase::addApplicationFont(":/fonts/Lato-Black.ttf");
|
||||
@@ -120,10 +120,26 @@ int main(int argc, char *argv[])
|
||||
QCommandLineOption c_autostart {{"a", "autostart"}, "System autostart"};
|
||||
parser.addOption(c_autostart);
|
||||
|
||||
QCommandLineOption c_cleanup {{"c", "cleanup"}, "Cleanup logs"};
|
||||
parser.addOption(c_cleanup);
|
||||
|
||||
parser.process(app);
|
||||
|
||||
if (!Debug::init()) {
|
||||
qWarning() << "Initialization of debug subsystem failed";
|
||||
if (parser.isSet(c_cleanup)) {
|
||||
Debug::cleanUp();
|
||||
QTimer::singleShot(100,[&app]{
|
||||
app.quit();
|
||||
});
|
||||
app.exec();
|
||||
return 0;
|
||||
}
|
||||
|
||||
Settings settings;
|
||||
|
||||
if (settings.isSaveLogs()) {
|
||||
if (!Debug::init()) {
|
||||
qWarning() << "Initialization of debug subsystem failed";
|
||||
}
|
||||
}
|
||||
|
||||
app.setQuitOnLastWindowClosed(false);
|
||||
@@ -165,6 +181,8 @@ int main(int argc, char *argv[])
|
||||
QCoreApplication::exit(-1);
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
engine->rootContext()->setContextProperty("Debug", &Debug::Instance());
|
||||
|
||||
engine->rootContext()->setContextProperty("UiLogic", uiLogic);
|
||||
|
||||
engine->rootContext()->setContextProperty("AppSettingsLogic", uiLogic->appSettingsLogic());
|
||||
@@ -196,26 +214,23 @@ int main(int argc, char *argv[])
|
||||
uiLogic->setQmlRoot(engine->rootObjects().at(0));
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
if (parser.isSet("a")) uiLogic->showOnStartup();
|
||||
else emit uiLogic->show();
|
||||
#else
|
||||
uiLogic->showOnStartup();
|
||||
#endif
|
||||
|
||||
// TODO - fix
|
||||
//#ifdef Q_OS_WIN
|
||||
// if (parser.isSet("a")) mainWindow.showOnStartup();
|
||||
// else mainWindow.show();
|
||||
//#else
|
||||
// mainWindow.showOnStartup();
|
||||
//#endif
|
||||
|
||||
|
||||
// TODO - fix
|
||||
//#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
// if (app.isPrimary()) {
|
||||
// QObject::connect(&app, &SingleApplication::instanceStarted, &mainWindow, [&](){
|
||||
// qDebug() << "Secondary instance started, showing this window instead";
|
||||
// mainWindow.show();
|
||||
// mainWindow.showNormal();
|
||||
// mainWindow.raise();
|
||||
// });
|
||||
// }
|
||||
//#endif
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
if (app.isPrimary()) {
|
||||
QObject::connect(&app, &SingleApplication::instanceStarted, uiLogic, [&](){
|
||||
qDebug() << "Secondary instance started, showing this window instead";
|
||||
emit uiLogic->show();
|
||||
emit uiLogic->raise();
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
36
client/platforms/linux/backendlogsobserver.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "backendlogsobserver.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusPendingReply>
|
||||
|
||||
namespace {
|
||||
Logger logger({LOG_LINUX, LOG_CONTROLLER}, "BackendLogsObserver");
|
||||
}
|
||||
|
||||
BackendLogsObserver::BackendLogsObserver(
|
||||
QObject* parent, std::function<void(const QString&)>&& callback)
|
||||
: QObject(parent), m_callback(std::move(callback)) {
|
||||
MVPN_COUNT_CTOR(BackendLogsObserver);
|
||||
}
|
||||
|
||||
BackendLogsObserver::~BackendLogsObserver() {
|
||||
MVPN_COUNT_DTOR(BackendLogsObserver);
|
||||
}
|
||||
|
||||
void BackendLogsObserver::completed(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<QString> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error received from the DBus service";
|
||||
m_callback("Failed to retrieve logs from the mozillavpn linuxdaemon.");
|
||||
return;
|
||||
}
|
||||
|
||||
QString status = reply.argumentAt<0>();
|
||||
m_callback(status);
|
||||
}
|
||||
29
client/platforms/linux/backendlogsobserver.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef BACKENDLOGSOBSERVER_H
|
||||
#define BACKENDLOGSOBSERVER_H
|
||||
|
||||
#include <functional>
|
||||
#include <QObject>
|
||||
|
||||
class QDBusPendingCallWatcher;
|
||||
|
||||
class BackendLogsObserver final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(BackendLogsObserver)
|
||||
|
||||
public:
|
||||
BackendLogsObserver(QObject* parent,
|
||||
std::function<void(const QString&)>&& callback);
|
||||
~BackendLogsObserver();
|
||||
|
||||
public slots:
|
||||
void completed(QDBusPendingCallWatcher* call);
|
||||
|
||||
private:
|
||||
std::function<void(const QString&)> m_callback;
|
||||
};
|
||||
|
||||
#endif // BACKENDLOGSOBSERVER_H
|
||||
109
client/platforms/linux/daemon/apptracker.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "apptracker.h"
|
||||
#include "dbustypeslinux.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
constexpr const char* GTK_DESKTOP_APP_SERVICE = "org.gtk.gio.DesktopAppInfo";
|
||||
constexpr const char* GTK_DESKTOP_APP_PATH = "/org/gtk/gio/DesktopAppInfo";
|
||||
|
||||
constexpr const char* DBUS_LOGIN_SERVICE = "org.freedesktop.login1";
|
||||
constexpr const char* DBUS_LOGIN_PATH = "/org/freedesktop/login1";
|
||||
constexpr const char* DBUS_LOGIN_MANAGER = "org.freedesktop.login1.Manager";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "AppTracker");
|
||||
}
|
||||
|
||||
AppTracker::AppTracker(QObject* parent) : QObject(parent) {
|
||||
MVPN_COUNT_CTOR(AppTracker);
|
||||
logger.debug() << "AppTracker created.";
|
||||
|
||||
QDBusConnection m_conn = QDBusConnection::systemBus();
|
||||
m_conn.connect("", DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "UserNew", this,
|
||||
SLOT(userCreated(uint, const QDBusObjectPath&)));
|
||||
m_conn.connect("", DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER, "UserRemoved", this,
|
||||
SLOT(userRemoved(uint, const QDBusObjectPath&)));
|
||||
|
||||
QDBusInterface n(DBUS_LOGIN_SERVICE, DBUS_LOGIN_PATH, DBUS_LOGIN_MANAGER,
|
||||
m_conn);
|
||||
QDBusPendingReply<UserDataList> reply = n.asyncCall("ListUsers");
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(userListCompleted(QDBusPendingCallWatcher*)));
|
||||
}
|
||||
|
||||
AppTracker::~AppTracker() {
|
||||
MVPN_COUNT_DTOR(AppTracker);
|
||||
logger.debug() << "AppTracker destroyed.";
|
||||
}
|
||||
|
||||
void AppTracker::userListCompleted(QDBusPendingCallWatcher* watcher) {
|
||||
QDBusPendingReply<UserDataList> reply = *watcher;
|
||||
if (reply.isValid()) {
|
||||
UserDataList list = reply.value();
|
||||
for (auto user : list) {
|
||||
userCreated(user.userid, user.path);
|
||||
}
|
||||
}
|
||||
|
||||
delete watcher;
|
||||
}
|
||||
|
||||
void AppTracker::userCreated(uint userid, const QDBusObjectPath& path) {
|
||||
logger.debug() << "User created uid:" << userid << "at:" << path.path();
|
||||
|
||||
/* Acquire the effective UID of the user to connect to their session bus. */
|
||||
uid_t realuid = getuid();
|
||||
if (seteuid(userid) < 0) {
|
||||
logger.warning() << "Failed to set effective UID";
|
||||
}
|
||||
auto guard = qScopeGuard([&] {
|
||||
if (seteuid(realuid) < 0) {
|
||||
logger.warning() << "Failed to restore effective UID";
|
||||
}
|
||||
});
|
||||
|
||||
/* For correctness we should ask systemd for the user's runtime directory. */
|
||||
QString busPath = "unix:path=/run/user/" + QString::number(userid) + "/bus";
|
||||
logger.debug() << "Connection to" << busPath;
|
||||
QDBusConnection connection =
|
||||
QDBusConnection::connectToBus(busPath, "user-" + QString::number(userid));
|
||||
|
||||
/* Connect to the user's GTK launch event. */
|
||||
bool isConnected = connection.connect(
|
||||
"", GTK_DESKTOP_APP_PATH, GTK_DESKTOP_APP_SERVICE, "Launched", this,
|
||||
SLOT(gtkLaunchEvent(const QByteArray&, const QString&, qlonglong,
|
||||
const QStringList&, const QVariantMap&)));
|
||||
if (!isConnected) {
|
||||
logger.warning() << "Failed to connect to GTK Launched signal";
|
||||
}
|
||||
}
|
||||
|
||||
void AppTracker::userRemoved(uint uid, const QDBusObjectPath& path) {
|
||||
logger.debug() << "User removed uid:" << uid << "at:" << path.path();
|
||||
QDBusConnection::disconnectFromBus("user-" + QString::number(uid));
|
||||
}
|
||||
|
||||
void AppTracker::gtkLaunchEvent(const QByteArray& appid, const QString& display,
|
||||
qlonglong pid, const QStringList& uris,
|
||||
const QVariantMap& extra) {
|
||||
Q_UNUSED(display);
|
||||
Q_UNUSED(uris);
|
||||
Q_UNUSED(extra);
|
||||
|
||||
QString appIdName(appid);
|
||||
if (!appIdName.isEmpty()) {
|
||||
emit appLaunched(appIdName, pid);
|
||||
}
|
||||
}
|
||||
31
client/platforms/linux/daemon/apptracker.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef APPTRACKER_H
|
||||
#define APPTRACKER_H
|
||||
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusObjectPath>
|
||||
|
||||
class AppTracker final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(AppTracker)
|
||||
|
||||
public:
|
||||
explicit AppTracker(QObject* parent);
|
||||
~AppTracker();
|
||||
|
||||
signals:
|
||||
void appLaunched(const QString& name, int rootpid);
|
||||
|
||||
private slots:
|
||||
void userListCompleted(QDBusPendingCallWatcher* call);
|
||||
void userCreated(uint uid, const QDBusObjectPath& path);
|
||||
void userRemoved(uint uid, const QDBusObjectPath& path);
|
||||
void gtkLaunchEvent(const QByteArray& appid, const QString& display,
|
||||
qlonglong pid, const QStringList& uris,
|
||||
const QVariantMap& extra);
|
||||
};
|
||||
|
||||
#endif // APPTRACKER_H
|
||||
249
client/platforms/linux/daemon/dbusservice.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dbusservice.h"
|
||||
#include "dbus_adaptor.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "loghandler.h"
|
||||
#include "polkithelper.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "DBusService");
|
||||
}
|
||||
|
||||
constexpr const char* APP_STATE_ACTIVE = "active";
|
||||
constexpr const char* APP_STATE_EXCLUDED = "excluded";
|
||||
constexpr const char* APP_STATE_BLOCKED = "blocked";
|
||||
|
||||
DBusService::DBusService(QObject* parent) : Daemon(parent) {
|
||||
MVPN_COUNT_CTOR(DBusService);
|
||||
|
||||
m_wgutils = new WireguardUtilsLinux(this);
|
||||
m_apptracker = new AppTracker(this);
|
||||
m_pidtracker = new PidTracker(this);
|
||||
|
||||
connect(m_apptracker, SIGNAL(appLaunched(const QString&, int)), this,
|
||||
SLOT(appLaunched(const QString&, int)));
|
||||
connect(m_pidtracker, SIGNAL(terminated(const QString&, int)), this,
|
||||
SLOT(appTerminated(const QString&, int)));
|
||||
|
||||
if (!removeInterfaceIfExists()) {
|
||||
qFatal("Interface `%s` exists and cannot be removed. Cannot proceed!",
|
||||
WG_INTERFACE);
|
||||
}
|
||||
}
|
||||
|
||||
DBusService::~DBusService() { MVPN_COUNT_DTOR(DBusService); }
|
||||
|
||||
IPUtils* DBusService::iputils() {
|
||||
if (!m_iputils) {
|
||||
m_iputils = new IPUtilsLinux(this);
|
||||
}
|
||||
return m_iputils;
|
||||
}
|
||||
|
||||
DnsUtils* DBusService::dnsutils() {
|
||||
if (!m_dnsutils) {
|
||||
m_dnsutils = new DnsUtilsLinux(this);
|
||||
}
|
||||
return m_dnsutils;
|
||||
}
|
||||
|
||||
void DBusService::setAdaptor(DbusAdaptor* adaptor) {
|
||||
Q_ASSERT(!m_adaptor);
|
||||
m_adaptor = adaptor;
|
||||
}
|
||||
|
||||
bool DBusService::removeInterfaceIfExists() {
|
||||
if (m_wgutils->interfaceExists()) {
|
||||
logger.warning() << "Device already exists. Let's remove it.";
|
||||
if (!m_wgutils->deleteInterface()) {
|
||||
logger.error() << "Failed to remove the device.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DBusService::version() {
|
||||
logger.debug() << "Version request";
|
||||
return PROTOCOL_VERSION;
|
||||
}
|
||||
|
||||
bool DBusService::activate(const QString& jsonConfig) {
|
||||
logger.debug() << "Activate";
|
||||
|
||||
if (!PolkitHelper::instance()->checkAuthorization(
|
||||
"org.mozilla.vpn.activate")) {
|
||||
logger.error() << "Polkit rejected";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(jsonConfig.toLocal8Bit());
|
||||
if (!json.isObject()) {
|
||||
logger.error() << "Invalid input";
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
|
||||
InterfaceConfig config;
|
||||
if (!parseConfig(obj, config)) {
|
||||
logger.error() << "Invalid configuration";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (obj.contains("vpnDisabledApps")) {
|
||||
QJsonArray disabledApps = obj["vpnDisabledApps"].toArray();
|
||||
for (const QJsonValue& app : disabledApps) {
|
||||
firewallApp(app.toString(), APP_STATE_EXCLUDED);
|
||||
}
|
||||
}
|
||||
|
||||
return Daemon::activate(config);
|
||||
}
|
||||
|
||||
bool DBusService::deactivate(bool emitSignals) {
|
||||
logger.debug() << "Deactivate";
|
||||
firewallClear();
|
||||
return Daemon::deactivate(emitSignals);
|
||||
}
|
||||
|
||||
QString DBusService::status() { return QString(getStatus()); }
|
||||
|
||||
QByteArray DBusService::getStatus() {
|
||||
logger.debug() << "Status request";
|
||||
QJsonObject json;
|
||||
|
||||
if (!m_connections.contains(0)) {
|
||||
json.insert("status", QJsonValue(false));
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
const InterfaceConfig& config = m_connections.value(0).m_config;
|
||||
if (!m_wgutils->interfaceExists()) {
|
||||
logger.error() << "Unable to get device";
|
||||
json.insert("status", QJsonValue(false));
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
json.insert("status", QJsonValue(true));
|
||||
json.insert("serverIpv4Gateway", QJsonValue(config.m_serverIpv4Gateway));
|
||||
json.insert("deviceIpv4Address", QJsonValue(config.m_deviceIpv4Address));
|
||||
WireguardUtilsLinux::peerStatus status =
|
||||
m_wgutils->getPeerStatus(config.m_serverPublicKey);
|
||||
json.insert("txBytes", QJsonValue(status.txBytes));
|
||||
json.insert("rxBytes", QJsonValue(status.rxBytes));
|
||||
|
||||
return QJsonDocument(json).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
QString DBusService::getLogs() {
|
||||
logger.debug() << "Log request";
|
||||
return Daemon::logs();
|
||||
}
|
||||
|
||||
void DBusService::appLaunched(const QString& name, int rootpid) {
|
||||
logger.debug() << "tracking:" << name << "PID:" << rootpid;
|
||||
ProcessGroup* group = m_pidtracker->track(name, rootpid);
|
||||
if (m_firewallApps.contains(name)) {
|
||||
group->state = m_firewallApps[name];
|
||||
group->moveToCgroup(getAppStateCgroup(group->state));
|
||||
}
|
||||
}
|
||||
|
||||
void DBusService::appTerminated(const QString& name, int rootpid) {
|
||||
logger.debug() << "terminate:" << name << "PID:" << rootpid;
|
||||
}
|
||||
|
||||
/* Get the list of running applications that the firewall knows about. */
|
||||
QString DBusService::runningApps() {
|
||||
QJsonArray result;
|
||||
for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) {
|
||||
const ProcessGroup* group = *i;
|
||||
QJsonObject appObject;
|
||||
QJsonArray pidList;
|
||||
appObject.insert("name", QJsonValue(group->name));
|
||||
appObject.insert("rootpid", QJsonValue(group->rootpid));
|
||||
appObject.insert("state", QJsonValue(group->state));
|
||||
|
||||
for (auto pid : group->kthreads.keys()) {
|
||||
pidList.append(QJsonValue(pid));
|
||||
}
|
||||
|
||||
appObject.insert("pids", pidList);
|
||||
result.append(appObject);
|
||||
}
|
||||
|
||||
return QJsonDocument(result).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
/* Update the firewall for running applications matching the application ID. */
|
||||
bool DBusService::firewallApp(const QString& appName, const QString& state) {
|
||||
logger.debug() << "Setting" << appName << "to firewall state" << state;
|
||||
m_firewallApps[appName] = state;
|
||||
QString cgroup = getAppStateCgroup(state);
|
||||
|
||||
/* Change matching applications' state to excluded */
|
||||
for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) {
|
||||
ProcessGroup* group = *i;
|
||||
if (group->name != appName) {
|
||||
continue;
|
||||
}
|
||||
group->state = state;
|
||||
group->moveToCgroup(cgroup);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Update the firewall for the application matching the desired PID. */
|
||||
bool DBusService::firewallPid(int rootpid, const QString& state) {
|
||||
ProcessGroup* group = m_pidtracker->group(rootpid);
|
||||
if (!group) {
|
||||
return false;
|
||||
}
|
||||
|
||||
group->state = state;
|
||||
group->moveToCgroup(getAppStateCgroup(group->state));
|
||||
|
||||
logger.debug() << "Setting" << group->name << "PID:" << rootpid
|
||||
<< "to firewall state" << state;
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Clear the firewall and return all applications to the active state */
|
||||
bool DBusService::firewallClear() {
|
||||
const QString cgroup = getAppStateCgroup(APP_STATE_ACTIVE);
|
||||
|
||||
m_firewallApps.clear();
|
||||
for (auto i = m_pidtracker->begin(); i != m_pidtracker->end(); i++) {
|
||||
ProcessGroup* group = *i;
|
||||
if (group->state == APP_STATE_ACTIVE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
group->state = APP_STATE_ACTIVE;
|
||||
group->moveToCgroup(cgroup);
|
||||
|
||||
logger.debug() << "Setting" << group->name << "PID:" << group->rootpid
|
||||
<< "to firewall state" << group->state;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
QString DBusService::getAppStateCgroup(const QString& state) {
|
||||
if (state == APP_STATE_EXCLUDED) {
|
||||
return m_wgutils->getExcludeCgroup();
|
||||
}
|
||||
if (state == APP_STATE_BLOCKED) {
|
||||
return m_wgutils->getBlockCgroup();
|
||||
}
|
||||
return m_wgutils->getDefaultCgroup();
|
||||
}
|
||||
72
client/platforms/linux/daemon/dbusservice.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DBUSSERVICE_H
|
||||
#define DBUSSERVICE_H
|
||||
|
||||
#include "daemon/daemon.h"
|
||||
#include "apptracker.h"
|
||||
#include "iputilslinux.h"
|
||||
#include "dnsutilslinux.h"
|
||||
#include "pidtracker.h"
|
||||
#include "wireguardutilslinux.h"
|
||||
|
||||
class DbusAdaptor;
|
||||
|
||||
class DBusService final : public Daemon {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DBusService)
|
||||
Q_CLASSINFO("D-Bus Interface", "org.mozilla.vpn.dbus")
|
||||
|
||||
public:
|
||||
DBusService(QObject* parent);
|
||||
~DBusService();
|
||||
|
||||
void setAdaptor(DbusAdaptor* adaptor);
|
||||
|
||||
using Daemon::activate;
|
||||
|
||||
public slots:
|
||||
bool activate(const QString& jsonConfig);
|
||||
|
||||
bool deactivate(bool emitSignals = true) override;
|
||||
QString status();
|
||||
|
||||
QString version();
|
||||
QString getLogs();
|
||||
|
||||
QString runningApps();
|
||||
bool firewallApp(const QString& appName, const QString& state);
|
||||
bool firewallPid(int rootpid, const QString& state);
|
||||
bool firewallClear();
|
||||
|
||||
protected:
|
||||
WireguardUtils* wgutils() const override { return m_wgutils; }
|
||||
bool supportIPUtils() const override { return true; }
|
||||
IPUtils* iputils() override;
|
||||
bool supportDnsUtils() const override { return true; }
|
||||
DnsUtils* dnsutils() override;
|
||||
|
||||
QByteArray getStatus() override;
|
||||
|
||||
private:
|
||||
bool removeInterfaceIfExists();
|
||||
QString getAppStateCgroup(const QString& state);
|
||||
|
||||
private slots:
|
||||
void appLaunched(const QString& name, int rootpid);
|
||||
void appTerminated(const QString& name, int rootpid);
|
||||
|
||||
private:
|
||||
DbusAdaptor* m_adaptor = nullptr;
|
||||
WireguardUtilsLinux* m_wgutils = nullptr;
|
||||
IPUtilsLinux* m_iputils = nullptr;
|
||||
DnsUtilsLinux* m_dnsutils = nullptr;
|
||||
|
||||
AppTracker* m_apptracker = nullptr;
|
||||
PidTracker* m_pidtracker = nullptr;
|
||||
QMap<QString, QString> m_firewallApps;
|
||||
};
|
||||
|
||||
#endif // DBUSSERVICE_H
|
||||
170
client/platforms/linux/daemon/dbustypeslinux.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DBUSTYPESLINUX_H
|
||||
#define DBUSTYPESLINUX_H
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDBusArgument>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <sys/socket.h>
|
||||
|
||||
/* D-Bus metatype for marshalling arguments to the SetLinkDNS method */
|
||||
class DnsResolver : public QHostAddress {
|
||||
public:
|
||||
DnsResolver(const QHostAddress& address = QHostAddress())
|
||||
: QHostAddress(address) {}
|
||||
|
||||
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsResolver& ip) {
|
||||
args.beginStructure();
|
||||
if (ip.protocol() == QAbstractSocket::IPv6Protocol) {
|
||||
Q_IPV6ADDR addrv6 = ip.toIPv6Address();
|
||||
args << AF_INET6;
|
||||
args << QByteArray::fromRawData((const char*)&addrv6, sizeof(addrv6));
|
||||
} else {
|
||||
quint32 addrv4 = ip.toIPv4Address();
|
||||
QByteArray data(4, 0);
|
||||
data[0] = (addrv4 >> 24) & 0xff;
|
||||
data[1] = (addrv4 >> 16) & 0xff;
|
||||
data[2] = (addrv4 >> 8) & 0xff;
|
||||
data[3] = (addrv4 >> 0) & 0xff;
|
||||
args << AF_INET;
|
||||
args << data;
|
||||
}
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||
DnsResolver& ip) {
|
||||
int family;
|
||||
QByteArray data;
|
||||
args.beginStructure();
|
||||
args >> family >> data;
|
||||
args.endStructure();
|
||||
if (family == AF_INET6) {
|
||||
ip.setAddress(data.constData());
|
||||
} else if (data.count() >= 4) {
|
||||
quint32 addrv4 = 0;
|
||||
addrv4 |= (data[0] << 24);
|
||||
addrv4 |= (data[1] << 16);
|
||||
addrv4 |= (data[2] << 8);
|
||||
addrv4 |= (data[3] << 0);
|
||||
ip.setAddress(addrv4);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
};
|
||||
typedef QList<DnsResolver> DnsResolverList;
|
||||
Q_DECLARE_METATYPE(DnsResolver);
|
||||
Q_DECLARE_METATYPE(DnsResolverList);
|
||||
|
||||
/* D-Bus metatype for marshalling arguments to the SetLinkDomains method */
|
||||
class DnsLinkDomain {
|
||||
public:
|
||||
DnsLinkDomain(const QString d = "", bool s = false) {
|
||||
domain = d;
|
||||
search = s;
|
||||
};
|
||||
QString domain;
|
||||
bool search;
|
||||
|
||||
friend QDBusArgument& operator<<(QDBusArgument& args,
|
||||
const DnsLinkDomain& data) {
|
||||
args.beginStructure();
|
||||
args << data.domain << data.search;
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||
DnsLinkDomain& data) {
|
||||
args.beginStructure();
|
||||
args >> data.domain >> data.search;
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
bool operator==(const DnsLinkDomain& other) const {
|
||||
return (domain == other.domain) && (search == other.search);
|
||||
}
|
||||
bool operator==(const QString& other) const { return (domain == other); }
|
||||
};
|
||||
typedef QList<DnsLinkDomain> DnsLinkDomainList;
|
||||
Q_DECLARE_METATYPE(DnsLinkDomain);
|
||||
Q_DECLARE_METATYPE(DnsLinkDomainList);
|
||||
|
||||
/* D-Bus metatype for marshalling the Domains property */
|
||||
class DnsDomain {
|
||||
public:
|
||||
DnsDomain() {}
|
||||
int ifindex = 0;
|
||||
QString domain = "";
|
||||
bool search = false;
|
||||
|
||||
friend QDBusArgument& operator<<(QDBusArgument& args, const DnsDomain& data) {
|
||||
args.beginStructure();
|
||||
args << data.ifindex << data.domain << data.search;
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||
DnsDomain& data) {
|
||||
args.beginStructure();
|
||||
args >> data.ifindex >> data.domain >> data.search;
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
};
|
||||
typedef QList<DnsDomain> DnsDomainList;
|
||||
Q_DECLARE_METATYPE(DnsDomain);
|
||||
Q_DECLARE_METATYPE(DnsDomainList);
|
||||
|
||||
/* D-Bus metatype for marshalling the freedesktop login manager data. */
|
||||
class UserData {
|
||||
public:
|
||||
QString name;
|
||||
uint userid;
|
||||
QDBusObjectPath path;
|
||||
|
||||
friend QDBusArgument& operator<<(QDBusArgument& args, const UserData& data) {
|
||||
args.beginStructure();
|
||||
args << data.userid << data.name << data.path;
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
friend const QDBusArgument& operator>>(const QDBusArgument& args,
|
||||
UserData& data) {
|
||||
args.beginStructure();
|
||||
args >> data.userid >> data.name >> data.path;
|
||||
args.endStructure();
|
||||
return args;
|
||||
}
|
||||
};
|
||||
typedef QList<UserData> UserDataList;
|
||||
Q_DECLARE_METATYPE(UserData);
|
||||
Q_DECLARE_METATYPE(UserDataList);
|
||||
|
||||
class DnsMetatypeRegistrationProxy {
|
||||
public:
|
||||
DnsMetatypeRegistrationProxy() {
|
||||
qRegisterMetaType<DnsResolver>();
|
||||
qDBusRegisterMetaType<DnsResolver>();
|
||||
qRegisterMetaType<DnsResolverList>();
|
||||
qDBusRegisterMetaType<DnsResolverList>();
|
||||
qRegisterMetaType<DnsLinkDomain>();
|
||||
qDBusRegisterMetaType<DnsLinkDomain>();
|
||||
qRegisterMetaType<DnsLinkDomainList>();
|
||||
qDBusRegisterMetaType<DnsLinkDomainList>();
|
||||
qRegisterMetaType<DnsDomain>();
|
||||
qDBusRegisterMetaType<DnsDomain>();
|
||||
qRegisterMetaType<DnsDomainList>();
|
||||
qDBusRegisterMetaType<DnsDomainList>();
|
||||
qRegisterMetaType<UserData>();
|
||||
qDBusRegisterMetaType<UserData>();
|
||||
qRegisterMetaType<UserDataList>();
|
||||
qDBusRegisterMetaType<UserDataList>();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // DBUSTYPESLINUX_H
|
||||
206
client/platforms/linux/daemon/dnsutilslinux.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dnsutilslinux.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDBusVariant>
|
||||
|
||||
#include <net/if.h>
|
||||
|
||||
constexpr const char* DBUS_RESOLVE_SERVICE = "org.freedesktop.resolve1";
|
||||
constexpr const char* DBUS_RESOLVE_PATH = "/org/freedesktop/resolve1";
|
||||
constexpr const char* DBUS_RESOLVE_MANAGER = "org.freedesktop.resolve1.Manager";
|
||||
constexpr const char* DBUS_PROPERTY_INTERFACE =
|
||||
"org.freedesktop.DBus.Properties";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "DnsUtilsLinux");
|
||||
}
|
||||
|
||||
DnsUtilsLinux::DnsUtilsLinux(QObject* parent) : DnsUtils(parent) {
|
||||
MVPN_COUNT_CTOR(DnsUtilsLinux);
|
||||
logger.debug() << "DnsUtilsLinux created.";
|
||||
|
||||
QDBusConnection conn = QDBusConnection::systemBus();
|
||||
m_resolver = new QDBusInterface(DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH,
|
||||
DBUS_RESOLVE_MANAGER, conn, this);
|
||||
}
|
||||
|
||||
DnsUtilsLinux::~DnsUtilsLinux() {
|
||||
MVPN_COUNT_DTOR(DnsUtilsLinux);
|
||||
|
||||
for (int ifindex : m_linkDomains.keys()) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(m_linkDomains[ifindex]);
|
||||
m_resolver->asyncCallWithArgumentList(QStringLiteral("SetLinkDomains"),
|
||||
argumentList);
|
||||
}
|
||||
|
||||
if (m_ifindex > 0) {
|
||||
m_resolver->asyncCall(QStringLiteral("RevertLink"), m_ifindex);
|
||||
}
|
||||
|
||||
logger.debug() << "DnsUtilsLinux destroyed.";
|
||||
}
|
||||
|
||||
bool DnsUtilsLinux::updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
m_ifindex = if_nametoindex(qPrintable(ifname));
|
||||
if (m_ifindex <= 0) {
|
||||
logger.error() << "Unable to resolve ifindex for" << ifname;
|
||||
return false;
|
||||
}
|
||||
|
||||
setLinkDNS(m_ifindex, resolvers);
|
||||
setLinkDefaultRoute(m_ifindex, true);
|
||||
updateLinkDomains();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DnsUtilsLinux::restoreResolvers() {
|
||||
for (auto ifindex : m_linkDomains.keys()) {
|
||||
setLinkDomains(ifindex, m_linkDomains[ifindex]);
|
||||
}
|
||||
m_linkDomains.clear();
|
||||
|
||||
/* Revert the VPN interface's DNS configuration */
|
||||
if (m_ifindex > 0) {
|
||||
QList<QVariant> argumentList = {QVariant::fromValue(m_ifindex)};
|
||||
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||
QStringLiteral("RevertLink"), argumentList);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||
|
||||
m_ifindex = 0;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::dnsCallCompleted(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error received from the DBus service";
|
||||
}
|
||||
delete call;
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDNS(int ifindex,
|
||||
const QList<QHostAddress>& resolvers) {
|
||||
QList<DnsResolver> resolverList;
|
||||
char ifnamebuf[IF_NAMESIZE];
|
||||
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||
for (auto ip : resolvers) {
|
||||
resolverList.append(ip);
|
||||
if (ifname) {
|
||||
logger.debug() << "Adding DNS resolver" << ip.toString() << "via"
|
||||
<< ifname;
|
||||
}
|
||||
}
|
||||
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(resolverList);
|
||||
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||
QStringLiteral("SetLinkDNS"), argumentList);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDomains(int ifindex,
|
||||
const QList<DnsLinkDomain>& domains) {
|
||||
char ifnamebuf[IF_NAMESIZE];
|
||||
const char* ifname = if_indextoname(ifindex, ifnamebuf);
|
||||
if (ifname) {
|
||||
for (auto d : domains) {
|
||||
logger.debug() << "Setting DNS domain:" << d.domain << "via" << ifname
|
||||
<< (d.search ? "search" : "");
|
||||
}
|
||||
}
|
||||
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(domains);
|
||||
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||
QStringLiteral("SetLinkDomains"), argumentList);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::setLinkDefaultRoute(int ifindex, bool enable) {
|
||||
QList<QVariant> argumentList;
|
||||
argumentList << QVariant::fromValue(ifindex);
|
||||
argumentList << QVariant::fromValue(enable);
|
||||
QDBusPendingReply<> reply = m_resolver->asyncCallWithArgumentList(
|
||||
QStringLiteral("SetLinkDefaultRoute"), argumentList);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(dnsCallCompleted(QDBusPendingCallWatcher*)));
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::updateLinkDomains() {
|
||||
/* Get the list of search domains, and remove any others that might conspire
|
||||
* to satisfy DNS resolution. Unfortunately, this is a pain because Qt doesn't
|
||||
* seem to be able to demarshall complex property types.
|
||||
*/
|
||||
QDBusMessage message = QDBusMessage::createMethodCall(
|
||||
DBUS_RESOLVE_SERVICE, DBUS_RESOLVE_PATH, DBUS_PROPERTY_INTERFACE, "Get");
|
||||
message << QString(DBUS_RESOLVE_MANAGER);
|
||||
message << QString("Domains");
|
||||
QDBusPendingReply<QVariant> reply =
|
||||
m_resolver->connection().asyncCall(message);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), this,
|
||||
SLOT(dnsDomainsReceived(QDBusPendingCallWatcher*)));
|
||||
}
|
||||
|
||||
void DnsUtilsLinux::dnsDomainsReceived(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<QVariant> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error retrieving the DNS domains from the DBus service";
|
||||
delete call;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Update the state of the DNS domains */
|
||||
m_linkDomains.clear();
|
||||
QDBusArgument args = qvariant_cast<QDBusArgument>(reply.value());
|
||||
QList<DnsDomain> list = qdbus_cast<QList<DnsDomain>>(args);
|
||||
for (auto d : list) {
|
||||
if (d.ifindex == 0) {
|
||||
continue;
|
||||
}
|
||||
m_linkDomains[d.ifindex].append(DnsLinkDomain(d.domain, d.search));
|
||||
}
|
||||
|
||||
/* Drop any competing root search domains. */
|
||||
DnsLinkDomain root = DnsLinkDomain(".", true);
|
||||
for (auto ifindex : m_linkDomains.keys()) {
|
||||
if (!m_linkDomains[ifindex].contains(root)) {
|
||||
continue;
|
||||
}
|
||||
QList<DnsLinkDomain> newlist = m_linkDomains[ifindex];
|
||||
newlist.removeAll(root);
|
||||
setLinkDomains(ifindex, newlist);
|
||||
}
|
||||
|
||||
/* Add a root search domain for the new interface. */
|
||||
QList<DnsLinkDomain> newlist = {root};
|
||||
setLinkDomains(m_ifindex, newlist);
|
||||
delete call;
|
||||
}
|
||||
|
||||
static DnsMetatypeRegistrationProxy s_dnsMetatypeProxy;
|
||||
41
client/platforms/linux/daemon/dnsutilslinux.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DNSUTILSLINUX_H
|
||||
#define DNSUTILSLINUX_H
|
||||
|
||||
#include "daemon/dnsutils.h"
|
||||
#include "dbustypeslinux.h"
|
||||
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
|
||||
class DnsUtilsLinux final : public DnsUtils {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DnsUtilsLinux)
|
||||
|
||||
public:
|
||||
DnsUtilsLinux(QObject* parent);
|
||||
~DnsUtilsLinux();
|
||||
bool updateResolvers(const QString& ifname,
|
||||
const QList<QHostAddress>& resolvers) override;
|
||||
bool restoreResolvers() override;
|
||||
|
||||
private:
|
||||
void setLinkDNS(int ifindex, const QList<QHostAddress>& resolvers);
|
||||
void setLinkDomains(int ifindex, const QList<DnsLinkDomain>& domains);
|
||||
void setLinkDefaultRoute(int ifindex, bool enable);
|
||||
void updateLinkDomains();
|
||||
|
||||
private slots:
|
||||
void dnsCallCompleted(QDBusPendingCallWatcher*);
|
||||
void dnsDomainsReceived(QDBusPendingCallWatcher*);
|
||||
|
||||
private:
|
||||
int m_ifindex = 0;
|
||||
QMap<int, DnsLinkDomainList> m_linkDomains;
|
||||
QDBusInterface* m_resolver = nullptr;
|
||||
};
|
||||
|
||||
#endif // DNSUTILSLINUX_H
|
||||
150
client/platforms/linux/daemon/iputilslinux.cpp
Normal file
@@ -0,0 +1,150 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "iputilslinux.h"
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <net/if.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
constexpr uint32_t ETH_MTU = 1500;
|
||||
constexpr uint32_t WG_MTU_OVERHEAD = 80;
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "IPUtilsLinux");
|
||||
}
|
||||
|
||||
IPUtilsLinux::IPUtilsLinux(QObject* parent) : IPUtils(parent) {
|
||||
MVPN_COUNT_CTOR(IPUtilsLinux);
|
||||
logger.debug() << "IPUtilsLinux created.";
|
||||
}
|
||||
|
||||
IPUtilsLinux::~IPUtilsLinux() {
|
||||
MVPN_COUNT_DTOR(IPUtilsLinux);
|
||||
logger.debug() << "IPUtilsLinux destroyed.";
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addInterfaceIPs(const InterfaceConfig& config) {
|
||||
return addIP4AddressToDevice(config) && addIP6AddressToDevice(config);
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::setMTUAndUp(const InterfaceConfig& config) {
|
||||
Q_UNUSED(config);
|
||||
|
||||
// Create socket file descriptor to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Setup the interface to interact with
|
||||
struct ifreq ifr;
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
|
||||
// MTU
|
||||
// FIXME: We need to know how many layers deep this particular
|
||||
// interface is into a tunnel to work effectively. Otherwise
|
||||
// we will run into fragmentation issues.
|
||||
ifr.ifr_mtu = ETH_MTU - WG_MTU_OVERHEAD;
|
||||
int ret = ioctl(sockfd, SIOCSIFMTU, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set MTU -- Return code: " << ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Up
|
||||
ifr.ifr_flags |= (IFF_UP | IFF_RUNNING);
|
||||
ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set device up -- Return code: " << ret;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addIP4AddressToDevice(const InterfaceConfig& config) {
|
||||
struct ifreq ifr;
|
||||
struct sockaddr_in* ifrAddr = (struct sockaddr_in*)&ifr.ifr_addr;
|
||||
|
||||
// Name the interface and set family
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
ifr.ifr_addr.sa_family = AF_INET;
|
||||
|
||||
// Get the device address to add to interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv4Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
inet_pton(AF_INET, deviceAddr, &ifrAddr->sin_addr);
|
||||
|
||||
// Create IPv4 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Set ifr to interface
|
||||
int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to set IPv4: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IPUtilsLinux::addIP6AddressToDevice(const InterfaceConfig& config) {
|
||||
// Set up the ifr and the companion ifr6
|
||||
struct in6_ifreq ifr6;
|
||||
ifr6.prefixlen = 64;
|
||||
|
||||
// Get the device address to add to ifr6 interface
|
||||
QPair<QHostAddress, int> parsedAddr =
|
||||
QHostAddress::parseSubnet(config.m_deviceIpv6Address);
|
||||
QByteArray _deviceAddr = parsedAddr.first.toString().toLocal8Bit();
|
||||
char* deviceAddr = _deviceAddr.data();
|
||||
inet_pton(AF_INET6, deviceAddr, &ifr6.addr);
|
||||
|
||||
// Create IPv6 socket to perform the ioctl operations on
|
||||
int sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
|
||||
if (sockfd < 0) {
|
||||
logger.error() << "Failed to create ioctl socket.";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { close(sockfd); });
|
||||
|
||||
// Get the index of named ifr and link with ifr6
|
||||
struct ifreq ifr;
|
||||
strncpy(ifr.ifr_name, WG_INTERFACE, IFNAMSIZ);
|
||||
ifr.ifr_addr.sa_family = AF_INET6;
|
||||
int ret = ioctl(sockfd, SIOGIFINDEX, &ifr);
|
||||
if (ret) {
|
||||
logger.error() << "Failed to get ifindex. Return code: " << ret;
|
||||
return false;
|
||||
}
|
||||
ifr6.ifindex = ifr.ifr_ifindex;
|
||||
|
||||
// Set ifr6 to the interface
|
||||
ret = ioctl(sockfd, SIOCSIFADDR, &ifr6);
|
||||
if (ret && (errno != EEXIST)) {
|
||||
logger.error() << "Failed to set IPv6: " << deviceAddr
|
||||
<< "error:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
31
client/platforms/linux/daemon/iputilslinux.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef IPUTILSLINUX_H
|
||||
#define IPUTILSLINUX_H
|
||||
|
||||
#include "daemon/iputils.h"
|
||||
|
||||
#include <arpa/inet.h>
|
||||
|
||||
class IPUtilsLinux final : public IPUtils {
|
||||
public:
|
||||
IPUtilsLinux(QObject* parent);
|
||||
~IPUtilsLinux();
|
||||
bool addInterfaceIPs(const InterfaceConfig& config) override;
|
||||
bool setMTUAndUp(const InterfaceConfig& config) override;
|
||||
|
||||
private:
|
||||
bool addIP4AddressToDevice(const InterfaceConfig& config);
|
||||
bool addIP6AddressToDevice(const InterfaceConfig& config);
|
||||
|
||||
private:
|
||||
struct in6_ifreq {
|
||||
struct in6_addr addr;
|
||||
uint32_t prefixlen;
|
||||
unsigned int ifindex;
|
||||
};
|
||||
};
|
||||
|
||||
#endif // IPUTILSLINUX_H
|
||||
58
client/platforms/linux/daemon/linuxdaemon.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "command.h"
|
||||
#include "dbusservice.h"
|
||||
#include "dbus_adaptor.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "loghandler.h"
|
||||
#include "signalhandler.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "main");
|
||||
}
|
||||
|
||||
class CommandLinuxDaemon final : public Command {
|
||||
public:
|
||||
explicit CommandLinuxDaemon(QObject* parent)
|
||||
: Command(parent, "linuxdaemon", "Starts the linux daemon") {
|
||||
MVPN_COUNT_CTOR(CommandLinuxDaemon);
|
||||
}
|
||||
|
||||
~CommandLinuxDaemon() { MVPN_COUNT_DTOR(CommandLinuxDaemon); }
|
||||
|
||||
int run(QStringList& tokens) override {
|
||||
Q_ASSERT(!tokens.isEmpty());
|
||||
LogHandler::setLocation("/var/log");
|
||||
|
||||
return runCommandLineApp([&]() {
|
||||
DBusService* dbus = new DBusService(qApp);
|
||||
DbusAdaptor* adaptor = new DbusAdaptor(dbus);
|
||||
dbus->setAdaptor(adaptor);
|
||||
|
||||
QDBusConnection connection = QDBusConnection::systemBus();
|
||||
logger.debug() << "Connecting to DBus...";
|
||||
|
||||
if (!connection.registerService("org.mozilla.vpn.dbus") ||
|
||||
!connection.registerObject("/", dbus)) {
|
||||
logger.error() << "Connection failed - name:"
|
||||
<< connection.lastError().name()
|
||||
<< "message:" << connection.lastError().message();
|
||||
return 1;
|
||||
}
|
||||
|
||||
SignalHandler sh;
|
||||
QObject::connect(&sh, &SignalHandler::quitRequested, [&]() {
|
||||
dbus->deactivate();
|
||||
qApp->quit();
|
||||
});
|
||||
|
||||
logger.debug() << "Ready!";
|
||||
return qApp->exec();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
static Command::RegistrationProxy<CommandLinuxDaemon> s_commandLinuxDaemon;
|
||||
28
client/platforms/linux/daemon/org.mozilla.vpn.conf
Normal file
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> <!-- -*- XML -*- -->
|
||||
|
||||
<!DOCTYPE busconfig PUBLIC
|
||||
"-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
|
||||
<busconfig>
|
||||
|
||||
<!-- Only root can own the service -->
|
||||
<policy user="root">
|
||||
<allow own="org.mozilla.vpn.dbus"/>
|
||||
</policy>
|
||||
|
||||
|
||||
<!-- Allow anyone to invoke methods on the interfaces -->
|
||||
<policy context="default">
|
||||
<allow own="org.mozilla.vpn.dbus"/>
|
||||
<allow send_destination="org.mozilla.vpn.dbus"/>
|
||||
|
||||
<allow send_destination="org.mozilla.vpn.dbus"
|
||||
send_interface="org.freedesktop.DBus.Introspectable"/>
|
||||
<allow send_destination="org.mozilla.vpn.dbus"
|
||||
send_interface="org.freedesktop.DBus.Properties"/>
|
||||
<allow send_destination="org.mozilla.vpn.dbus"
|
||||
send_interface="org.freedesktop.DBus.Properties"/>
|
||||
</policy>
|
||||
|
||||
</busconfig>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
[D-BUS Service]
|
||||
User=root
|
||||
Name=org.mozilla.vpn.dbus
|
||||
Exec=/usr/bin/mozillavpn linuxdaemon
|
||||
SystemdService=mozillavpn.service
|
||||
46
client/platforms/linux/daemon/org.mozilla.vpn.dbus.xml
Normal file
@@ -0,0 +1,46 @@
|
||||
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
|
||||
<node>
|
||||
<interface name="org.mozilla.vpn.dbus">
|
||||
<method name="version">
|
||||
<arg type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="activate">
|
||||
<arg type="b" direction="out"/>
|
||||
<arg name="jsonConfig" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="deactivate">
|
||||
<arg type="b" direction="out"/>
|
||||
</method>
|
||||
<method name="status">
|
||||
<arg name="jsonStatus" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="runningApps">
|
||||
<arg type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="firewallApp">
|
||||
<arg type="b" direction="out"/>
|
||||
<arg name="appName" type="s" direction="in"/>
|
||||
<arg name="state" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="firewallPid">
|
||||
<arg type="b" direction="out"/>
|
||||
<arg name="rootpid" type="i" direction="in"/>
|
||||
<arg name="state" type="s" direction="in"/>
|
||||
</method>
|
||||
<method name="firewallClear">
|
||||
<arg type="b" direction="out"/>
|
||||
</method>
|
||||
<method name="getLogs">
|
||||
<arg name="logs" type="s" direction="out"/>
|
||||
</method>
|
||||
<method name="cleanupLogs">
|
||||
</method>
|
||||
<signal name="connected">
|
||||
<arg name="hopindex" type="i" direction="out"/>
|
||||
</signal>
|
||||
<signal name="disconnected">
|
||||
<arg name="hopindex" type="i" direction="out"/>
|
||||
</signal>
|
||||
</interface>
|
||||
</node>
|
||||
|
||||
25
client/platforms/linux/daemon/org.mozilla.vpn.policy
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE policyconfig PUBLIC
|
||||
"-//freedesktop//DTD PolicyKit Policy Configuration 1.0//EN"
|
||||
"http://www.freedesktop.org/standards/PolicyKit/1.0/policyconfig.dtd">
|
||||
<policyconfig>
|
||||
|
||||
<action id="org.mozilla.vpn.activate">
|
||||
<description>Activate the Mozilla VPN</description>
|
||||
<message>Activate the Mozilla VPN</message>
|
||||
<defaults>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
|
||||
<action id="org.mozilla.vpn.deactivate">
|
||||
<description>Deactivate the Mozilla VPN</description>
|
||||
<message>Deactivate the Mozilla VPN</message>
|
||||
<defaults>
|
||||
<allow_inactive>no</allow_inactive>
|
||||
<allow_active>auth_admin</allow_active>
|
||||
</defaults>
|
||||
</action>
|
||||
</policyconfig>
|
||||
|
||||
227
client/platforms/linux/daemon/pidtracker.cpp
Normal file
@@ -0,0 +1,227 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "pidtracker.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <limits.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/connector.h>
|
||||
#include <linux/cn_proc.h>
|
||||
|
||||
constexpr size_t CN_MCAST_MSG_SIZE =
|
||||
sizeof(struct cn_msg) + sizeof(enum proc_cn_mcast_op);
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "PidTracker");
|
||||
}
|
||||
|
||||
PidTracker::PidTracker(QObject* parent) : QObject(parent) {
|
||||
MVPN_COUNT_CTOR(PidTracker);
|
||||
logger.debug() << "PidTracker created.";
|
||||
|
||||
m_nlsock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
|
||||
if (m_nlsock < 0) {
|
||||
logger.error() << "Failed to create netlink socket:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
nladdr.nl_groups = CN_IDX_PROC;
|
||||
nladdr.nl_pid = getpid();
|
||||
nladdr.nl_pad = 0;
|
||||
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) < 0) {
|
||||
logger.error() << "Failed to bind netlink socket:" << strerror(errno);
|
||||
close(m_nlsock);
|
||||
m_nlsock = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[NLMSG_SPACE(CN_MCAST_MSG_SIZE)];
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
struct cn_msg* cnmsg = (struct cn_msg*)NLMSG_DATA(nlmsg);
|
||||
enum proc_cn_mcast_op mcast_op = PROC_CN_MCAST_LISTEN;
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(CN_MCAST_MSG_SIZE);
|
||||
nlmsg->nlmsg_type = NLMSG_DONE;
|
||||
nlmsg->nlmsg_flags = 0;
|
||||
nlmsg->nlmsg_seq = 0;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
|
||||
cnmsg->id.idx = CN_IDX_PROC;
|
||||
cnmsg->id.val = CN_VAL_PROC;
|
||||
cnmsg->seq = 0;
|
||||
cnmsg->ack = 0;
|
||||
cnmsg->len = sizeof(mcast_op);
|
||||
memcpy(cnmsg->data, &mcast_op, sizeof(mcast_op));
|
||||
|
||||
if (send(m_nlsock, nlmsg, sizeof(buf), 0) != sizeof(buf)) {
|
||||
logger.error() << "Failed to send netlink message:" << strerror(errno);
|
||||
close(m_nlsock);
|
||||
m_nlsock = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
m_socket = new QSocketNotifier(m_nlsock, QSocketNotifier::Read, this);
|
||||
connect(m_socket, &QSocketNotifier::activated, this, &PidTracker::readData);
|
||||
}
|
||||
|
||||
PidTracker::~PidTracker() {
|
||||
MVPN_COUNT_DTOR(PidTracker);
|
||||
logger.debug() << "PidTracker destroyed.";
|
||||
|
||||
m_processTree.clear();
|
||||
while (!m_processGroups.isEmpty()) {
|
||||
ProcessGroup* group = m_processGroups.takeFirst();
|
||||
delete group;
|
||||
}
|
||||
|
||||
if (m_nlsock > 0) {
|
||||
close(m_nlsock);
|
||||
}
|
||||
}
|
||||
|
||||
ProcessGroup* PidTracker::track(const QString& name, int rootpid) {
|
||||
ProcessGroup* group = m_processTree.value(rootpid, nullptr);
|
||||
if (group) {
|
||||
logger.warning() << "Ignoring attempt to track duplicate PID";
|
||||
return group;
|
||||
}
|
||||
group = new ProcessGroup(name, rootpid);
|
||||
group->kthreads[rootpid] = 1;
|
||||
group->refcount = 1;
|
||||
|
||||
m_processGroups.append(group);
|
||||
m_processTree[rootpid] = group;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
void PidTracker::handleProcEvent(struct cn_msg* cnmsg) {
|
||||
struct proc_event* ev = (struct proc_event*)cnmsg->data;
|
||||
|
||||
if (ev->what == proc_event::PROC_EVENT_FORK) {
|
||||
auto forkdata = &ev->event_data.fork;
|
||||
/* If the child process already exists, track a new kernel thread. */
|
||||
ProcessGroup* group = m_processTree.value(forkdata->child_tgid, nullptr);
|
||||
if (group) {
|
||||
group->kthreads[forkdata->child_tgid]++;
|
||||
return;
|
||||
}
|
||||
|
||||
/* Track a new userspace process if was forked from a known parent. */
|
||||
group = m_processTree.value(forkdata->parent_tgid, nullptr);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
m_processTree[forkdata->child_tgid] = group;
|
||||
group->kthreads[forkdata->child_tgid] = 1;
|
||||
group->refcount++;
|
||||
emit pidForked(group->name, forkdata->parent_tgid, forkdata->child_tgid);
|
||||
}
|
||||
|
||||
if (ev->what == proc_event::PROC_EVENT_EXIT) {
|
||||
auto exitdata = &ev->event_data.exit;
|
||||
ProcessGroup* group = m_processTree.value(exitdata->process_tgid, nullptr);
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Decrement the number of kernel threads in this userspace process. */
|
||||
uint threadcount = group->kthreads.value(exitdata->process_tgid, 0);
|
||||
if (threadcount == 0) {
|
||||
return;
|
||||
}
|
||||
if (threadcount > 1) {
|
||||
group->kthreads[exitdata->process_tgid] = threadcount - 1;
|
||||
return;
|
||||
}
|
||||
group->kthreads.remove(exitdata->process_tgid);
|
||||
|
||||
/* A userspace process exits when all of its kernel threads exit. */
|
||||
Q_ASSERT(group->refcount > 0);
|
||||
group->refcount--;
|
||||
if (group->refcount == 0) {
|
||||
emit terminated(group->name, group->rootpid);
|
||||
m_processGroups.removeAll(group);
|
||||
delete group;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PidTracker::readData() {
|
||||
struct sockaddr_nl src;
|
||||
socklen_t srclen = sizeof(src);
|
||||
ssize_t recvlen;
|
||||
|
||||
recvlen = recvfrom(m_nlsock, m_readBuf, sizeof(m_readBuf), MSG_DONTWAIT,
|
||||
(struct sockaddr*)&src, &srclen);
|
||||
if (recvlen == ENOBUFS) {
|
||||
logger.error()
|
||||
<< "Failed to read netlink socket: buffer full, message dropped";
|
||||
return;
|
||||
}
|
||||
if (recvlen < 0) {
|
||||
logger.error() << "Failed to read netlink socket:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
if (srclen != sizeof(src)) {
|
||||
logger.error() << "Failed to read netlink socket: invalid address length";
|
||||
return;
|
||||
}
|
||||
|
||||
/* We are only interested in process-control messages from the kernel */
|
||||
if ((src.nl_groups != CN_IDX_PROC) || (src.nl_pid != 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Handle the process-control messages. */
|
||||
struct nlmsghdr* msg;
|
||||
for (msg = (struct nlmsghdr*)m_readBuf; NLMSG_OK(msg, recvlen);
|
||||
msg = NLMSG_NEXT(msg, recvlen)) {
|
||||
struct cn_msg* cnmsg = (struct cn_msg*)NLMSG_DATA(msg);
|
||||
if (msg->nlmsg_type == NLMSG_NOOP) {
|
||||
continue;
|
||||
}
|
||||
if ((msg->nlmsg_type == NLMSG_ERROR) ||
|
||||
(msg->nlmsg_type == NLMSG_OVERRUN)) {
|
||||
break;
|
||||
}
|
||||
handleProcEvent(cnmsg);
|
||||
if (msg->nlmsg_type == NLMSG_DONE) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ProcessGroup::moveToCgroup(const QString& name) {
|
||||
/* Do nothing if Cgroups are not supported. */
|
||||
if (name.isNull()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
QString cgProcsFile = name + "/cgroup.procs";
|
||||
FILE* fp = fopen(qPrintable(cgProcsFile), "w");
|
||||
if (!fp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto pid : kthreads.keys()) {
|
||||
fprintf(fp, "%d\n", pid);
|
||||
fflush(fp);
|
||||
}
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
72
client/platforms/linux/daemon/pidtracker.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef PIDTRACKER_H
|
||||
#define PIDTRACKER_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QSocketNotifier>
|
||||
#include <QString>
|
||||
|
||||
#include "leakdetector.h"
|
||||
|
||||
struct cn_msg;
|
||||
|
||||
class ProcessGroup {
|
||||
public:
|
||||
ProcessGroup(const QString& groupName, int groupRootPid,
|
||||
const QString& groupState = "active") {
|
||||
MVPN_COUNT_CTOR(ProcessGroup);
|
||||
name = groupName;
|
||||
rootpid = groupRootPid;
|
||||
state = groupState;
|
||||
refcount = 0;
|
||||
}
|
||||
~ProcessGroup() { MVPN_COUNT_DTOR(ProcessGroup); }
|
||||
|
||||
bool moveToCgroup(const QString& name);
|
||||
|
||||
QHash<int, uint> kthreads;
|
||||
QString name;
|
||||
QString state;
|
||||
int rootpid;
|
||||
int refcount;
|
||||
};
|
||||
|
||||
class PidTracker final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(PidTracker)
|
||||
|
||||
public:
|
||||
explicit PidTracker(QObject* parent);
|
||||
~PidTracker();
|
||||
|
||||
ProcessGroup* track(const QString& name, int rootpid);
|
||||
|
||||
QList<int> pids() { return m_processTree.keys(); }
|
||||
QList<ProcessGroup*>::iterator begin() { return m_processGroups.begin(); }
|
||||
QList<ProcessGroup*>::iterator end() { return m_processGroups.end(); }
|
||||
ProcessGroup* group(int pid) { return m_processTree.value(pid); }
|
||||
|
||||
signals:
|
||||
void pidForked(const QString& name, int parent, int child);
|
||||
void pidExited(const QString& name, int pid);
|
||||
void terminated(const QString& name, int rootpid);
|
||||
|
||||
private:
|
||||
void handleProcEvent(struct cn_msg*);
|
||||
|
||||
private slots:
|
||||
void readData();
|
||||
|
||||
private:
|
||||
int m_nlsock;
|
||||
char m_readBuf[2048];
|
||||
QSocketNotifier* m_socket = nullptr;
|
||||
QHash<int, ProcessGroup*> m_processTree;
|
||||
QList<ProcessGroup*> m_processGroups;
|
||||
};
|
||||
|
||||
#endif // PIDTRACKER_H
|
||||
66
client/platforms/linux/daemon/polkithelper.cpp
Normal file
@@ -0,0 +1,66 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "polkithelper.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
// No extra QT includes after this line!
|
||||
#undef Q_SIGNALS
|
||||
#include "polkit/polkit.h"
|
||||
|
||||
class Helper final {
|
||||
public:
|
||||
Helper() = default;
|
||||
~Helper() {
|
||||
if (m_error) {
|
||||
g_error_free(m_error);
|
||||
}
|
||||
|
||||
if (m_subject) {
|
||||
g_object_unref(m_subject);
|
||||
}
|
||||
|
||||
if (m_result) {
|
||||
g_object_unref(m_result);
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
GError* m_error = nullptr;
|
||||
PolkitSubject* m_subject = nullptr;
|
||||
PolkitAuthorizationResult* m_result = nullptr;
|
||||
};
|
||||
|
||||
// static
|
||||
PolkitHelper* PolkitHelper::instance() {
|
||||
static PolkitHelper s_instance;
|
||||
return &s_instance;
|
||||
}
|
||||
|
||||
bool PolkitHelper::checkAuthorization(const QString& actionId) {
|
||||
qDebug() << "Check Authorization for" << actionId;
|
||||
|
||||
Helper h;
|
||||
|
||||
PolkitAuthority* authority = polkit_authority_get_sync(NULL, &h.m_error);
|
||||
if (h.m_error) {
|
||||
qDebug() << "Fail to generate a polkit authority object:"
|
||||
<< h.m_error->message;
|
||||
return false;
|
||||
}
|
||||
|
||||
h.m_subject = polkit_unix_process_new_for_owner(getpid(), 0, -1);
|
||||
|
||||
h.m_result = polkit_authority_check_authorization_sync(
|
||||
authority, h.m_subject, actionId.toLatin1().data(), nullptr,
|
||||
POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, nullptr,
|
||||
&h.m_error);
|
||||
if (h.m_error) {
|
||||
qDebug() << "Authorization sync failed:" << h.m_error->message;
|
||||
return false;
|
||||
}
|
||||
|
||||
return polkit_authorization_result_get_is_authorized(h.m_result);
|
||||
}
|
||||
23
client/platforms/linux/daemon/polkithelper.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef POLKITHELPER_H
|
||||
#define POLKITHELPER_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
class PolkitHelper final {
|
||||
public:
|
||||
static PolkitHelper* instance();
|
||||
|
||||
bool checkAuthorization(const QString& actionId);
|
||||
|
||||
private:
|
||||
PolkitHelper() = default;
|
||||
~PolkitHelper() = default;
|
||||
|
||||
Q_DISABLE_COPY(PolkitHelper)
|
||||
};
|
||||
|
||||
#endif // POLKITHELPER_H
|
||||
648
client/platforms/linux/daemon/wireguardutilslinux.cpp
Normal file
@@ -0,0 +1,648 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "wireguardutilslinux.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "platforms/linux/linuxdependencies.h"
|
||||
|
||||
#include <QHostAddress>
|
||||
#include <QScopeGuard>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/fib_rules.h>
|
||||
#include <linux/netlink.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
#include <mntent.h>
|
||||
#include <net/if.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Import wireguard C library for Linux
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
#include "../../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h"
|
||||
#include "../../linux/netfilter/netfilter.h"
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif
|
||||
// End import wireguard
|
||||
|
||||
/* Packets sent outside the VPN need to be marked for the routing policy
|
||||
* to direct them appropriately. The value of the mark and the table ID
|
||||
* aren't important, so long as they are unique.
|
||||
*/
|
||||
constexpr uint32_t WG_FIREWALL_MARK = 0xca6c;
|
||||
constexpr uint32_t WG_ROUTE_TABLE = 0xca6c;
|
||||
|
||||
/* Traffic classifiers can be used to mark packets which should be either
|
||||
* excluded from the VPN tunnel, or blocked entirely. The values of these
|
||||
* classifiers aren't important so long as they are unique.
|
||||
*/
|
||||
constexpr const char* VPN_EXCLUDE_CGROUP = "/mozvpn.exclude";
|
||||
constexpr const char* VPN_BLOCK_CGROUP = "/mozvpn.block";
|
||||
constexpr uint32_t VPN_EXCLUDE_CLASS_ID = 0x00110011;
|
||||
constexpr uint32_t VPN_BLOCK_CLASS_ID = 0x00220022;
|
||||
|
||||
static void nlmsg_append_attr(char* buf, size_t maxlen, int attrtype,
|
||||
const void* attrdata, size_t attrlen);
|
||||
static void nlmsg_append_attr32(char* buf, size_t maxlen, int attrtype,
|
||||
uint32_t value);
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "WireguardUtilsLinux");
|
||||
|
||||
void NetfilterLogger(int level, const char* msg) {
|
||||
Q_UNUSED(level);
|
||||
logger.debug() << "NetfilterGo:" << msg;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent)
|
||||
: WireguardUtils(parent) {
|
||||
MVPN_COUNT_CTOR(WireguardUtilsLinux);
|
||||
NetfilterSetLogger((GoUintptr)&NetfilterLogger);
|
||||
NetfilterCreateTables();
|
||||
|
||||
m_nlsock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
|
||||
if (m_nlsock < 0) {
|
||||
logger.warning() << "Failed to create netlink socket:" << strerror(errno);
|
||||
}
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
nladdr.nl_pid = getpid();
|
||||
if (bind(m_nlsock, (struct sockaddr*)&nladdr, sizeof(nladdr)) != 0) {
|
||||
logger.warning() << "Failed to bind netlink socket:" << strerror(errno);
|
||||
}
|
||||
|
||||
m_notifier = new QSocketNotifier(m_nlsock, QSocketNotifier::Read, this);
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&WireguardUtilsLinux::nlsockReady);
|
||||
|
||||
/* Create control groups for split tunnelling */
|
||||
m_cgroups = LinuxDependencies::findCgroupPath("net_cls");
|
||||
if (!m_cgroups.isNull()) {
|
||||
if (!setupCgroupClass(m_cgroups + VPN_EXCLUDE_CGROUP,
|
||||
VPN_EXCLUDE_CLASS_ID)) {
|
||||
m_cgroups.clear();
|
||||
} else if (!setupCgroupClass(m_cgroups + VPN_BLOCK_CGROUP,
|
||||
VPN_BLOCK_CLASS_ID)) {
|
||||
m_cgroups.clear();
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug() << "WireguardUtilsLinux created.";
|
||||
}
|
||||
|
||||
WireguardUtilsLinux::~WireguardUtilsLinux() {
|
||||
MVPN_COUNT_DTOR(WireguardUtilsLinux);
|
||||
NetfilterRemoveTables();
|
||||
if (m_nlsock >= 0) {
|
||||
close(m_nlsock);
|
||||
}
|
||||
logger.debug() << "WireguardUtilsLinux destroyed.";
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::interfaceExists() {
|
||||
// As currentInterfaces only gets wireguard interfaces, this method
|
||||
// also confirms an interface as being a wireguard interface.
|
||||
return currentInterfaces().contains(WG_INTERFACE);
|
||||
};
|
||||
|
||||
bool WireguardUtilsLinux::addInterface(const InterfaceConfig& config) {
|
||||
int code = wg_add_device(WG_INTERFACE);
|
||||
if (code != 0) {
|
||||
logger.error() << "Adding interface failed:" << strerror(-code);
|
||||
return false;
|
||||
}
|
||||
|
||||
wg_device* device = static_cast<wg_device*>(calloc(1, sizeof(*device)));
|
||||
if (!device) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { wg_free_device(device); });
|
||||
|
||||
// Name
|
||||
strncpy(device->name, WG_INTERFACE, IFNAMSIZ);
|
||||
// Private Key
|
||||
wg_key_from_base64(device->private_key, config.m_privateKey.toLocal8Bit());
|
||||
|
||||
// Set/update device
|
||||
device->fwmark = WG_FIREWALL_MARK;
|
||||
device->flags = (wg_device_flags)(
|
||||
WGDEVICE_HAS_PRIVATE_KEY | WGDEVICE_REPLACE_PEERS | WGDEVICE_HAS_FWMARK);
|
||||
if (wg_set_device(device) != 0) {
|
||||
logger.error() << "Failed to setup the device";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create routing policy rules
|
||||
if (!rtmSendRule(RTM_NEWRULE,
|
||||
NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK,
|
||||
AF_INET)) {
|
||||
return false;
|
||||
}
|
||||
if (!rtmSendRule(RTM_NEWRULE,
|
||||
NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK,
|
||||
AF_INET6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Configure firewall rules
|
||||
GoString goIfname = {.p = device->name, .n = (ptrdiff_t)strlen(device->name)};
|
||||
if (NetfilterIfup(goIfname, device->fwmark) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (!m_cgroups.isNull()) {
|
||||
NetfilterMarkCgroup(VPN_EXCLUDE_CLASS_ID, device->fwmark);
|
||||
NetfilterBlockCgroup(VPN_BLOCK_CLASS_ID);
|
||||
}
|
||||
|
||||
int slashPos = config.m_deviceIpv6Address.indexOf('/');
|
||||
GoString goIpv6Address = {.p = qPrintable(config.m_deviceIpv6Address),
|
||||
.n = config.m_deviceIpv6Address.length()};
|
||||
if (slashPos != -1) {
|
||||
goIpv6Address.n = slashPos;
|
||||
}
|
||||
NetfilterIsolateIpv6(goIfname, goIpv6Address);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::updatePeer(const InterfaceConfig& config) {
|
||||
wg_device* device = static_cast<wg_device*>(calloc(1, sizeof(*device)));
|
||||
if (!device) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { wg_free_device(device); });
|
||||
|
||||
wg_peer* peer = static_cast<wg_peer*>(calloc(1, sizeof(*peer)));
|
||||
if (!peer) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
device->first_peer = device->last_peer = peer;
|
||||
|
||||
logger.debug() << "Adding peer" << printablePubkey(config.m_serverPublicKey);
|
||||
|
||||
// Public Key
|
||||
wg_key_from_base64(peer->public_key, qPrintable(config.m_serverPublicKey));
|
||||
// Endpoint
|
||||
if (!setPeerEndpoint(&peer->endpoint.addr, config.m_serverIpv4AddrIn,
|
||||
config.m_serverPort)) {
|
||||
logger.error() << "Failed to set peer endpoint for hop"
|
||||
<< config.m_hopindex;
|
||||
return false;
|
||||
}
|
||||
|
||||
// HACK: We are running into a crash on Linux due to the address list being
|
||||
// *WAAAY* too long, which we aren't really using anways since the routing
|
||||
// tables are doing all the work for us anyways.
|
||||
//
|
||||
// To work around the issue, just set default routes for hopindex zero.
|
||||
if (config.m_hopindex == 0) {
|
||||
if (!config.m_deviceIpv4Address.isNull()) {
|
||||
addPeerPrefix(peer, IPAddressRange("0.0.0.0", 0, IPAddressRange::IPv4));
|
||||
}
|
||||
if (!config.m_deviceIpv6Address.isNull()) {
|
||||
addPeerPrefix(peer, IPAddressRange("::", 0, IPAddressRange::IPv6));
|
||||
}
|
||||
} else {
|
||||
for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) {
|
||||
bool ok = addPeerPrefix(peer, ip);
|
||||
if (!ok) {
|
||||
logger.error() << "Invalid IP address:" << ip.ipAddress();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set/update peer
|
||||
strncpy(device->name, WG_INTERFACE, IFNAMSIZ);
|
||||
device->flags = (wg_device_flags)0;
|
||||
peer->flags =
|
||||
(wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS);
|
||||
if (wg_set_device(device) != 0) {
|
||||
logger.error() << "Failed to set the new peer hop" << config.m_hopindex;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deletePeer(const QString& pubkey) {
|
||||
wg_device* device = static_cast<wg_device*>(calloc(1, sizeof(*device)));
|
||||
if (!device) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
auto guard = qScopeGuard([&] { wg_free_device(device); });
|
||||
|
||||
wg_peer* peer = static_cast<wg_peer*>(calloc(1, sizeof(*peer)));
|
||||
if (!peer) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
device->first_peer = device->last_peer = peer;
|
||||
|
||||
logger.debug() << "Removing peer" << printablePubkey(pubkey);
|
||||
|
||||
// Public Key
|
||||
peer->flags = (wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REMOVE_ME);
|
||||
wg_key_from_base64(peer->public_key, qPrintable(pubkey));
|
||||
|
||||
// Set/update device
|
||||
strncpy(device->name, WG_INTERFACE, IFNAMSIZ);
|
||||
device->flags = (wg_device_flags)0;
|
||||
if (wg_set_device(device) != 0) {
|
||||
logger.error() << "Failed to remove the peer";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deleteInterface() {
|
||||
// Clear firewall rules
|
||||
NetfilterClearTables();
|
||||
|
||||
// Clear routing policy rules
|
||||
if (!rtmSendRule(RTM_DELRULE, NLM_F_REQUEST | NLM_F_ACK, AF_INET)) {
|
||||
return false;
|
||||
}
|
||||
if (!rtmSendRule(RTM_DELRULE, NLM_F_REQUEST | NLM_F_ACK, AF_INET6)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete the interface
|
||||
int returnCode = wg_del_device(WG_INTERFACE);
|
||||
if (returnCode != 0) {
|
||||
logger.error() << "Deleting interface failed:" << strerror(-returnCode);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
WireguardUtils::peerStatus WireguardUtilsLinux::getPeerStatus(
|
||||
const QString& pubkey) {
|
||||
wg_device* device = nullptr;
|
||||
wg_peer* peer = nullptr;
|
||||
peerStatus status = {0, 0};
|
||||
|
||||
if (wg_get_device(&device, WG_INTERFACE) != 0) {
|
||||
logger.warning() << "Unable to get stats for" << WG_INTERFACE;
|
||||
return status;
|
||||
}
|
||||
|
||||
wg_key key;
|
||||
wg_key_from_base64(key, qPrintable(pubkey));
|
||||
wg_for_each_peer(device, peer) {
|
||||
if (memcmp(&key, &peer->public_key, sizeof(key)) != 0) {
|
||||
continue;
|
||||
}
|
||||
status.txBytes = peer->tx_bytes;
|
||||
status.rxBytes = peer->rx_bytes;
|
||||
break;
|
||||
}
|
||||
wg_free_device(device);
|
||||
return status;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::updateRoutePrefix(const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
logger.debug() << "Adding route to" << prefix.toString();
|
||||
int flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE | NLM_F_ACK;
|
||||
return rtmSendRoute(RTM_NEWROUTE, flags, prefix, hopindex);
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::deleteRoutePrefix(const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
logger.debug() << "Removing route to" << prefix.toString();
|
||||
int flags = NLM_F_REQUEST | NLM_F_ACK;
|
||||
return rtmSendRoute(RTM_DELROUTE, flags, prefix, hopindex);
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::rtmSendRoute(int action, int flags,
|
||||
const IPAddressRange& prefix,
|
||||
int hopindex) {
|
||||
constexpr size_t rtm_max_size = sizeof(struct rtmsg) +
|
||||
2 * RTA_SPACE(sizeof(uint32_t)) +
|
||||
RTA_SPACE(sizeof(struct in6_addr));
|
||||
int index = if_nametoindex(WG_INTERFACE);
|
||||
if (index <= 0) {
|
||||
logger.error() << "if_nametoindex() failed:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
wg_allowedip ip;
|
||||
if (!buildAllowedIp(&ip, prefix)) {
|
||||
logger.warning() << "Invalid destination prefix";
|
||||
return false;
|
||||
}
|
||||
|
||||
char buf[NLMSG_SPACE(rtm_max_size)];
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
struct rtmsg* rtm = (struct rtmsg*)NLMSG_DATA(nlmsg);
|
||||
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
|
||||
nlmsg->nlmsg_type = action;
|
||||
nlmsg->nlmsg_flags = flags;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
nlmsg->nlmsg_seq = m_nlseq++;
|
||||
rtm->rtm_dst_len = ip.cidr;
|
||||
rtm->rtm_family = ip.family;
|
||||
rtm->rtm_type = RTN_UNICAST;
|
||||
rtm->rtm_protocol = RTPROT_BOOT;
|
||||
rtm->rtm_scope = RT_SCOPE_UNIVERSE;
|
||||
|
||||
// Routes for the main hop should be placed into their own table.
|
||||
if (hopindex == 0) {
|
||||
rtm->rtm_table = RT_TABLE_UNSPEC;
|
||||
nlmsg_append_attr32(buf, sizeof(buf), RTA_TABLE, WG_ROUTE_TABLE);
|
||||
} else {
|
||||
rtm->rtm_table = RT_TABLE_MAIN;
|
||||
}
|
||||
|
||||
if (rtm->rtm_family == AF_INET6) {
|
||||
nlmsg_append_attr(buf, sizeof(buf), RTA_DST, &ip.ip6, sizeof(ip.ip6));
|
||||
} else {
|
||||
nlmsg_append_attr(buf, sizeof(buf), RTA_DST, &ip.ip4, sizeof(ip.ip4));
|
||||
}
|
||||
nlmsg_append_attr32(buf, sizeof(buf), RTA_OIF, index);
|
||||
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
size_t result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0,
|
||||
(struct sockaddr*)&nladdr, sizeof(nladdr));
|
||||
return (result == nlmsg->nlmsg_len);
|
||||
}
|
||||
|
||||
// PRIVATE METHODS
|
||||
QStringList WireguardUtilsLinux::currentInterfaces() {
|
||||
char* deviceNames = wg_list_device_names();
|
||||
QStringList devices;
|
||||
if (!deviceNames) {
|
||||
return devices;
|
||||
}
|
||||
char* deviceName;
|
||||
size_t len;
|
||||
wg_for_each_device_name(deviceNames, deviceName, len) {
|
||||
devices.append(deviceName);
|
||||
}
|
||||
free(deviceNames);
|
||||
return devices;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::setPeerEndpoint(struct sockaddr* sa,
|
||||
const QString& address, int port) {
|
||||
QString portString = QString::number(port);
|
||||
|
||||
struct addrinfo hints;
|
||||
memset(&hints, 0, sizeof(hints));
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_DGRAM;
|
||||
hints.ai_protocol = IPPROTO_UDP;
|
||||
|
||||
struct addrinfo* resolved = nullptr;
|
||||
auto guard = qScopeGuard([&] { freeaddrinfo(resolved); });
|
||||
int retries = 15;
|
||||
|
||||
for (unsigned int timeout = 1000000;;
|
||||
timeout = std::min((unsigned int)20000000, timeout * 6 / 5)) {
|
||||
int rv = getaddrinfo(address.toLocal8Bit(), portString.toLocal8Bit(),
|
||||
&hints, &resolved);
|
||||
if (!rv) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* The set of return codes that are "permanent failures". All other
|
||||
* possibilities are potentially transient.
|
||||
*
|
||||
* This is according to https://sourceware.org/glibc/wiki/NameResolver which
|
||||
* states: "From the perspective of the application that calls getaddrinfo()
|
||||
* it perhaps doesn't matter that much since EAI_FAIL, EAI_NONAME and
|
||||
* EAI_NODATA are all permanent failure codes and the causes are all
|
||||
* permanent failures in the sense that there is no point in retrying
|
||||
* later."
|
||||
*
|
||||
* So this is what we do, except FreeBSD removed EAI_NODATA some time ago,
|
||||
* so that's conditional.
|
||||
*/
|
||||
if (rv == EAI_NONAME || rv == EAI_FAIL ||
|
||||
#ifdef EAI_NODATA
|
||||
rv == EAI_NODATA ||
|
||||
#endif
|
||||
(retries >= 0 && !retries--)) {
|
||||
logger.error() << "Failed to resolve the address endpoint";
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.warning() << "Trying again in" << (timeout / 1000000.0) << "seconds";
|
||||
usleep(timeout);
|
||||
}
|
||||
|
||||
if ((resolved->ai_family == AF_INET &&
|
||||
resolved->ai_addrlen == sizeof(struct sockaddr_in)) ||
|
||||
(resolved->ai_family == AF_INET6 &&
|
||||
resolved->ai_addrlen == sizeof(struct sockaddr_in6))) {
|
||||
memcpy(sa, resolved->ai_addr, resolved->ai_addrlen);
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.error() << "Invalid endpoint" << address;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::addPeerPrefix(wg_peer* peer,
|
||||
const IPAddressRange& prefix) {
|
||||
Q_ASSERT(peer);
|
||||
|
||||
wg_allowedip* allowedip =
|
||||
static_cast<wg_allowedip*>(calloc(1, sizeof(*allowedip)));
|
||||
if (!allowedip) {
|
||||
logger.error() << "Allocation failure";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!peer->first_allowedip) {
|
||||
peer->first_allowedip = allowedip;
|
||||
} else {
|
||||
peer->last_allowedip->next_allowedip = allowedip;
|
||||
}
|
||||
peer->last_allowedip = allowedip;
|
||||
|
||||
return buildAllowedIp(allowedip, prefix);
|
||||
}
|
||||
|
||||
static void nlmsg_append_attr(char* buf, size_t maxlen, int attrtype,
|
||||
const void* attrdata, size_t attrlen) {
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
size_t newlen = NLMSG_ALIGN(nlmsg->nlmsg_len) + RTA_SPACE(attrlen);
|
||||
if (newlen <= maxlen) {
|
||||
struct rtattr* attr = (struct rtattr*)(buf + NLMSG_ALIGN(nlmsg->nlmsg_len));
|
||||
attr->rta_type = attrtype;
|
||||
attr->rta_len = RTA_LENGTH(attrlen);
|
||||
memcpy(RTA_DATA(attr), attrdata, attrlen);
|
||||
nlmsg->nlmsg_len = newlen;
|
||||
}
|
||||
}
|
||||
|
||||
static void nlmsg_append_attr32(char* buf, size_t maxlen, int attrtype,
|
||||
uint32_t value) {
|
||||
nlmsg_append_attr(buf, maxlen, attrtype, &value, sizeof(value));
|
||||
}
|
||||
|
||||
bool WireguardUtilsLinux::rtmSendRule(int action, int flags, int addrfamily) {
|
||||
constexpr size_t fib_max_size =
|
||||
sizeof(struct fib_rule_hdr) + 2 * RTA_SPACE(sizeof(uint32_t));
|
||||
|
||||
char buf[NLMSG_SPACE(fib_max_size)];
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
struct fib_rule_hdr* rule = (struct fib_rule_hdr*)NLMSG_DATA(nlmsg);
|
||||
struct sockaddr_nl nladdr;
|
||||
memset(&nladdr, 0, sizeof(nladdr));
|
||||
nladdr.nl_family = AF_NETLINK;
|
||||
|
||||
/* Create a routing policy rule to select the wireguard routing table for
|
||||
* unmarked packets. This is equivalent to:
|
||||
* ip rule add not fwmark $WG_FIREWALL_MARK table $WG_ROUTE_TABLE
|
||||
*/
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr));
|
||||
nlmsg->nlmsg_type = action;
|
||||
nlmsg->nlmsg_flags = flags;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
nlmsg->nlmsg_seq = m_nlseq++;
|
||||
rule->family = addrfamily;
|
||||
rule->table = RT_TABLE_UNSPEC;
|
||||
rule->action = FR_ACT_TO_TBL;
|
||||
rule->flags = FIB_RULE_INVERT;
|
||||
nlmsg_append_attr32(buf, sizeof(buf), FRA_FWMARK, WG_FIREWALL_MARK);
|
||||
nlmsg_append_attr32(buf, sizeof(buf), FRA_TABLE, WG_ROUTE_TABLE);
|
||||
ssize_t result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0,
|
||||
(struct sockaddr*)&nladdr, sizeof(nladdr));
|
||||
if (result != nlmsg->nlmsg_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Create a routing policy rule to suppress zero-length prefix lookups from
|
||||
* in the main routing table. This is equivalent to:
|
||||
* ip rule add table main suppress_prefixlength 0
|
||||
*/
|
||||
memset(buf, 0, sizeof(buf));
|
||||
nlmsg->nlmsg_len = NLMSG_LENGTH(sizeof(struct fib_rule_hdr));
|
||||
nlmsg->nlmsg_type = action;
|
||||
nlmsg->nlmsg_flags = flags;
|
||||
nlmsg->nlmsg_pid = getpid();
|
||||
nlmsg->nlmsg_seq = m_nlseq++;
|
||||
rule->family = addrfamily;
|
||||
rule->table = RT_TABLE_MAIN;
|
||||
rule->action = FR_ACT_TO_TBL;
|
||||
rule->flags = 0;
|
||||
nlmsg_append_attr32(buf, sizeof(buf), FRA_SUPPRESS_PREFIXLEN, 0);
|
||||
result = sendto(m_nlsock, buf, nlmsg->nlmsg_len, 0, (struct sockaddr*)&nladdr,
|
||||
sizeof(nladdr));
|
||||
if (result != nlmsg->nlmsg_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WireguardUtilsLinux::nlsockReady() {
|
||||
char buf[1024];
|
||||
ssize_t len = recv(m_nlsock, buf, sizeof(buf), MSG_DONTWAIT);
|
||||
if (len <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct nlmsghdr* nlmsg = (struct nlmsghdr*)buf;
|
||||
while (NLMSG_OK(nlmsg, len)) {
|
||||
if (nlmsg->nlmsg_type == NLMSG_DONE) {
|
||||
return;
|
||||
}
|
||||
if (nlmsg->nlmsg_type != NLMSG_ERROR) {
|
||||
nlmsg = NLMSG_NEXT(nlmsg, len);
|
||||
continue;
|
||||
}
|
||||
struct nlmsgerr* err = (struct nlmsgerr*)NLMSG_DATA(nlmsg);
|
||||
if (err->error != 0) {
|
||||
logger.debug() << "Netlink request failed:" << strerror(-err->error);
|
||||
}
|
||||
nlmsg = NLMSG_NEXT(nlmsg, len);
|
||||
}
|
||||
}
|
||||
|
||||
// static
|
||||
bool WireguardUtilsLinux::setupCgroupClass(const QString& path,
|
||||
unsigned long classid) {
|
||||
logger.debug() << "Creating control group:" << path;
|
||||
int flags = S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH;
|
||||
int err = mkdir(qPrintable(path), flags);
|
||||
if ((err < 0) && (errno != EEXIST)) {
|
||||
logger.error() << "Failed to create" << path + ":" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
QString netClassPath = path + "/net_cls.classid";
|
||||
FILE* fp = fopen(qPrintable(netClassPath), "w");
|
||||
if (!fp) {
|
||||
logger.error() << "Failed to set classid:" << strerror(errno);
|
||||
return false;
|
||||
}
|
||||
fprintf(fp, "%lu", classid);
|
||||
fclose(fp);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString WireguardUtilsLinux::getExcludeCgroup() const {
|
||||
if (m_cgroups.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return m_cgroups + VPN_EXCLUDE_CGROUP;
|
||||
}
|
||||
|
||||
QString WireguardUtilsLinux::getBlockCgroup() const {
|
||||
if (m_cgroups.isNull()) {
|
||||
return QString();
|
||||
}
|
||||
return m_cgroups + VPN_BLOCK_CGROUP;
|
||||
}
|
||||
|
||||
// static
|
||||
bool WireguardUtilsLinux::buildAllowedIp(wg_allowedip* ip,
|
||||
const IPAddressRange& prefix) {
|
||||
if (prefix.type() == IPAddressRange::IPv4) {
|
||||
ip->family = AF_INET;
|
||||
ip->cidr = prefix.range();
|
||||
return inet_pton(AF_INET, qPrintable(prefix.ipAddress()), &ip->ip4) == 1;
|
||||
}
|
||||
if (prefix.type() == IPAddressRange::IPv6) {
|
||||
ip->family = AF_INET6;
|
||||
ip->cidr = prefix.range();
|
||||
return inet_pton(AF_INET6, qPrintable(prefix.ipAddress()), &ip->ip6) == 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
QString WireguardUtilsLinux::printablePubkey(const QString& pubkey) {
|
||||
if (pubkey.length() < 12) {
|
||||
return pubkey;
|
||||
} else {
|
||||
return pubkey.left(6) + "..." + pubkey.right(6);
|
||||
}
|
||||
}
|
||||
56
client/platforms/linux/daemon/wireguardutilslinux.h
Normal file
@@ -0,0 +1,56 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef WIREGUARDUTILSLINUX_H
|
||||
#define WIREGUARDUTILSLINUX_H
|
||||
|
||||
#include "daemon/wireguardutils.h"
|
||||
#include <QObject>
|
||||
#include <QSocketNotifier>
|
||||
#include <QStringList>
|
||||
|
||||
class WireguardUtilsLinux final : public WireguardUtils {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
WireguardUtilsLinux(QObject* parent);
|
||||
~WireguardUtilsLinux();
|
||||
bool interfaceExists() override;
|
||||
bool addInterface(const InterfaceConfig& config) override;
|
||||
bool deleteInterface() override;
|
||||
|
||||
bool updatePeer(const InterfaceConfig& config) override;
|
||||
bool deletePeer(const QString& pubkey) override;
|
||||
peerStatus getPeerStatus(const QString& pubkey) override;
|
||||
|
||||
bool updateRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
bool deleteRoutePrefix(const IPAddressRange& prefix, int hopindex) override;
|
||||
|
||||
QString getDefaultCgroup() const { return m_cgroups; }
|
||||
QString getExcludeCgroup() const;
|
||||
QString getBlockCgroup() const;
|
||||
|
||||
private:
|
||||
QStringList currentInterfaces();
|
||||
bool setPeerEndpoint(struct sockaddr* sa, const QString& address, int port);
|
||||
bool addPeerPrefix(struct wg_peer* peer, const IPAddressRange& prefix);
|
||||
bool rtmSendRule(int action, int flags, int addrfamily);
|
||||
bool rtmSendRoute(int action, int flags, const IPAddressRange& prefix,
|
||||
int hopindex);
|
||||
static bool setupCgroupClass(const QString& path, unsigned long classid);
|
||||
static bool buildAllowedIp(struct wg_allowedip*,
|
||||
const IPAddressRange& prefix);
|
||||
|
||||
static QString printablePubkey(const QString& pubkey);
|
||||
|
||||
int m_nlsock = -1;
|
||||
int m_nlseq = 0;
|
||||
QSocketNotifier* m_notifier = nullptr;
|
||||
QString m_cgroups;
|
||||
|
||||
private slots:
|
||||
void nlsockReady();
|
||||
};
|
||||
|
||||
#endif // WIREGUARDUTILSLINUX_H
|
||||
126
client/platforms/linux/dbusclient.cpp
Normal file
@@ -0,0 +1,126 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "dbusclient.h"
|
||||
#include "ipaddressrange.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "models/device.h"
|
||||
#include "models/keys.h"
|
||||
#include "models/server.h"
|
||||
#include "mozillavpn.h"
|
||||
#include "settingsholder.h"
|
||||
|
||||
#include <QDBusPendingCall>
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QDBusPendingReply>
|
||||
|
||||
constexpr const char* DBUS_SERVICE = "org.mozilla.vpn.dbus";
|
||||
constexpr const char* DBUS_PATH = "/";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "DBusClient");
|
||||
}
|
||||
|
||||
DBusClient::DBusClient(QObject* parent) : QObject(parent) {
|
||||
MVPN_COUNT_CTOR(DBusClient);
|
||||
|
||||
m_dbus = new OrgMozillaVpnDbusInterface(DBUS_SERVICE, DBUS_PATH,
|
||||
QDBusConnection::systemBus(), this);
|
||||
|
||||
connect(m_dbus, &OrgMozillaVpnDbusInterface::connected, this,
|
||||
&DBusClient::connected);
|
||||
connect(m_dbus, &OrgMozillaVpnDbusInterface::disconnected, this,
|
||||
&DBusClient::disconnected);
|
||||
}
|
||||
|
||||
DBusClient::~DBusClient() { MVPN_COUNT_DTOR(DBusClient); }
|
||||
|
||||
QDBusPendingCallWatcher* DBusClient::version() {
|
||||
logger.debug() << "Version via DBus";
|
||||
QDBusPendingReply<QString> reply = m_dbus->version();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
|
||||
&QDBusPendingCallWatcher::deleteLater);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
QDBusPendingCallWatcher* DBusClient::activate(
|
||||
const Server& server, const Device* device, const Keys* keys, int hopindex,
|
||||
const QList<IPAddressRange>& allowedIPAddressRanges,
|
||||
const QStringList& vpnDisabledApps, const QHostAddress& dnsServer) {
|
||||
QJsonObject json;
|
||||
json.insert("privateKey", QJsonValue(keys->privateKey()));
|
||||
json.insert("deviceIpv4Address", QJsonValue(device->ipv4Address()));
|
||||
json.insert("deviceIpv6Address", QJsonValue(device->ipv6Address()));
|
||||
json.insert("serverIpv4Gateway", QJsonValue(server.ipv4Gateway()));
|
||||
json.insert("serverIpv6Gateway", QJsonValue(server.ipv6Gateway()));
|
||||
json.insert("serverPublicKey", QJsonValue(server.publicKey()));
|
||||
json.insert("serverIpv4AddrIn", QJsonValue(server.ipv4AddrIn()));
|
||||
json.insert("serverIpv6AddrIn", QJsonValue(server.ipv6AddrIn()));
|
||||
json.insert("serverPort", QJsonValue((double)server.choosePort()));
|
||||
json.insert("dnsServer", QJsonValue(dnsServer.toString()));
|
||||
json.insert("hopindex", QJsonValue((double)hopindex));
|
||||
|
||||
QJsonArray allowedIPAddesses;
|
||||
for (const IPAddressRange& i : allowedIPAddressRanges) {
|
||||
QJsonObject range;
|
||||
range.insert("address", QJsonValue(i.ipAddress()));
|
||||
range.insert("range", QJsonValue((double)i.range()));
|
||||
range.insert("isIpv6", QJsonValue(i.type() == IPAddressRange::IPv6));
|
||||
allowedIPAddesses.append(range);
|
||||
};
|
||||
json.insert("allowedIPAddressRanges", allowedIPAddesses);
|
||||
|
||||
QJsonArray disabledApps;
|
||||
for (const QString& i : vpnDisabledApps) {
|
||||
disabledApps.append(QJsonValue(i));
|
||||
logger.debug() << "Disabling:" << i;
|
||||
}
|
||||
json.insert("vpnDisabledApps", disabledApps);
|
||||
|
||||
logger.debug() << "Activate via DBus";
|
||||
QDBusPendingReply<bool> reply =
|
||||
m_dbus->activate(QJsonDocument(json).toJson(QJsonDocument::Compact));
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
|
||||
&QDBusPendingCallWatcher::deleteLater);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
QDBusPendingCallWatcher* DBusClient::deactivate() {
|
||||
logger.debug() << "Deactivate via DBus";
|
||||
QDBusPendingReply<bool> reply = m_dbus->deactivate();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
|
||||
&QDBusPendingCallWatcher::deleteLater);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
QDBusPendingCallWatcher* DBusClient::status() {
|
||||
logger.debug() << "Status via DBus";
|
||||
QDBusPendingReply<QString> reply = m_dbus->status();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
|
||||
&QDBusPendingCallWatcher::deleteLater);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
QDBusPendingCallWatcher* DBusClient::getLogs() {
|
||||
logger.debug() << "Get logs via DBus";
|
||||
QDBusPendingReply<QString> reply = m_dbus->getLogs();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
|
||||
&QDBusPendingCallWatcher::deleteLater);
|
||||
return watcher;
|
||||
}
|
||||
|
||||
QDBusPendingCallWatcher* DBusClient::cleanupLogs() {
|
||||
logger.debug() << "Cleanup logs via DBus";
|
||||
QDBusPendingReply<QString> reply = m_dbus->cleanupLogs();
|
||||
QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this);
|
||||
QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
|
||||
&QDBusPendingCallWatcher::deleteLater);
|
||||
return watcher;
|
||||
}
|
||||
51
client/platforms/linux/dbusclient.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef DBUSCLIENT_H
|
||||
#define DBUSCLIENT_H
|
||||
|
||||
#include "dbus_interface.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
class Server;
|
||||
class Device;
|
||||
class Keys;
|
||||
class IPAddressRange;
|
||||
class QDBusPendingCallWatcher;
|
||||
|
||||
class DBusClient final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(DBusClient)
|
||||
|
||||
public:
|
||||
DBusClient(QObject* parent);
|
||||
~DBusClient();
|
||||
|
||||
QDBusPendingCallWatcher* version();
|
||||
|
||||
QDBusPendingCallWatcher* activate(
|
||||
const Server& server, const Device* device, const Keys* keys,
|
||||
int hopindex, const QList<IPAddressRange>& allowedIPAddressRanges,
|
||||
const QStringList& vpnDisabledApps, const QHostAddress& dnsServer);
|
||||
|
||||
QDBusPendingCallWatcher* deactivate();
|
||||
|
||||
QDBusPendingCallWatcher* status();
|
||||
|
||||
QDBusPendingCallWatcher* getLogs();
|
||||
|
||||
QDBusPendingCallWatcher* cleanupLogs();
|
||||
|
||||
signals:
|
||||
void connected(int hopindex);
|
||||
void disconnected(int hopindex);
|
||||
|
||||
private:
|
||||
OrgMozillaVpnDbusInterface* m_dbus;
|
||||
};
|
||||
|
||||
#endif // DBUSCLIENT_H
|
||||
79
client/platforms/linux/linuxappimageprovider.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxappimageprovider.h"
|
||||
#include "logger.h"
|
||||
#include "leakdetector.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QString>
|
||||
#include <QSettings>
|
||||
#include <QIcon>
|
||||
|
||||
constexpr const char* PIXMAP_FALLBACK_PATH = "/usr/share/pixmaps/";
|
||||
constexpr const char* DESKTOP_ICON_LOCATION = "/usr/share/icons/";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_CONTROLLER, "LinuxAppImageProvider");
|
||||
}
|
||||
|
||||
LinuxAppImageProvider::LinuxAppImageProvider(QObject* parent)
|
||||
: AppImageProvider(parent, QQuickImageProvider::Image,
|
||||
QQmlImageProviderBase::ForceAsynchronousImageLoading) {
|
||||
MVPN_COUNT_CTOR(LinuxAppImageProvider);
|
||||
|
||||
QStringList searchPaths = QIcon::fallbackSearchPaths();
|
||||
|
||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
if (pe.contains("XDG_DATA_DIRS")) {
|
||||
QStringList parts = pe.value("XDG_DATA_DIRS").split(":");
|
||||
for (const QString& part : parts) {
|
||||
addFallbackPaths(part + "/icons", searchPaths);
|
||||
}
|
||||
} else {
|
||||
addFallbackPaths(DESKTOP_ICON_LOCATION, searchPaths);
|
||||
}
|
||||
|
||||
if (pe.contains("HOME")) {
|
||||
addFallbackPaths(pe.value("HOME") + "/.local/share/icons", searchPaths);
|
||||
}
|
||||
|
||||
searchPaths << PIXMAP_FALLBACK_PATH;
|
||||
QIcon::setFallbackSearchPaths(searchPaths);
|
||||
}
|
||||
|
||||
LinuxAppImageProvider::~LinuxAppImageProvider() {
|
||||
MVPN_COUNT_DTOR(LinuxAppImageProvider);
|
||||
}
|
||||
|
||||
void LinuxAppImageProvider::addFallbackPaths(const QString& iconDir,
|
||||
QStringList& searchPaths) {
|
||||
searchPaths << iconDir;
|
||||
|
||||
QDirIterator iter(iconDir, QDir::Dirs | QDir::NoDotAndDotDot);
|
||||
while (iter.hasNext()) {
|
||||
QFileInfo fileinfo(iter.next());
|
||||
logger.debug() << "Adding QIcon fallback:" << fileinfo.absoluteFilePath();
|
||||
searchPaths << fileinfo.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
|
||||
// from QQuickImageProvider
|
||||
QImage LinuxAppImageProvider::requestImage(const QString& id, QSize* size,
|
||||
const QSize& requestedSize) {
|
||||
QSettings entry(id, QSettings::IniFormat);
|
||||
entry.beginGroup("Desktop Entry");
|
||||
QString name = entry.value("Icon").toString();
|
||||
|
||||
QIcon icon = QIcon::fromTheme(name);
|
||||
QPixmap pixmap = icon.pixmap(requestedSize);
|
||||
size->setHeight(pixmap.height());
|
||||
size->setWidth(pixmap.width());
|
||||
logger.debug() << "Loaded icon" << icon.name() << "size:" << pixmap.width()
|
||||
<< "x" << pixmap.height();
|
||||
|
||||
return pixmap.toImage();
|
||||
}
|
||||
22
client/platforms/linux/linuxappimageprovider.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXAPPIMAGEPROVIDER_H
|
||||
#define LINUXAPPIMAGEPROVIDER_H
|
||||
|
||||
#include "appimageprovider.h"
|
||||
|
||||
class LinuxAppImageProvider final : public AppImageProvider {
|
||||
public:
|
||||
LinuxAppImageProvider(QObject* parent);
|
||||
~LinuxAppImageProvider();
|
||||
QImage requestImage(const QString& id, QSize* size,
|
||||
const QSize& requestedSize) override;
|
||||
|
||||
private:
|
||||
static void addFallbackPaths(const QString& dataDir,
|
||||
QStringList& fallbackPaths);
|
||||
};
|
||||
|
||||
#endif // LINUXAPPIMAGEPROVIDER_H
|
||||
74
client/platforms/linux/linuxapplistprovider.cpp
Normal file
@@ -0,0 +1,74 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxapplistprovider.h"
|
||||
#include "leakdetector.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QSettings>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
#include "logger.h"
|
||||
#include "leakdetector.h"
|
||||
|
||||
constexpr const char* DESKTOP_ENTRY_LOCATION = "/usr/share/applications/";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_CONTROLLER, "LinuxAppListProvider");
|
||||
}
|
||||
|
||||
LinuxAppListProvider::LinuxAppListProvider(QObject* parent)
|
||||
: AppListProvider(parent) {
|
||||
MVPN_COUNT_CTOR(LinuxAppListProvider);
|
||||
}
|
||||
|
||||
LinuxAppListProvider::~LinuxAppListProvider() {
|
||||
MVPN_COUNT_DTOR(LinuxAppListProvider);
|
||||
}
|
||||
|
||||
void LinuxAppListProvider::fetchEntries(const QString& dataDir,
|
||||
QMap<QString, QString>& map) {
|
||||
logger.debug() << "Fetch Application list from" << dataDir;
|
||||
|
||||
QDirIterator iter(dataDir, QStringList() << "*.desktop", QDir::Files);
|
||||
while (iter.hasNext()) {
|
||||
QFileInfo fileinfo(iter.next());
|
||||
QSettings entry(fileinfo.filePath(), QSettings::IniFormat);
|
||||
entry.beginGroup("Desktop Entry");
|
||||
|
||||
/* Filter out everything except visible applications. */
|
||||
if (entry.value("Type").toString() != "Application") {
|
||||
continue;
|
||||
}
|
||||
if (entry.value("NoDisplay", QVariant(false)).toBool()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
map[fileinfo.absoluteFilePath()] = entry.value("Name").toString();
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxAppListProvider::getApplicationList() {
|
||||
logger.debug() << "Fetch Application list from Linux desktop";
|
||||
QMap<QString, QString> out;
|
||||
|
||||
QProcessEnvironment pe = QProcessEnvironment::systemEnvironment();
|
||||
if (pe.contains("XDG_DATA_DIRS")) {
|
||||
QStringList parts = pe.value("XDG_DATA_DIRS").split(":");
|
||||
for (const QString& part : parts) {
|
||||
fetchEntries(part.trimmed() + "/applications", out);
|
||||
}
|
||||
} else {
|
||||
fetchEntries(DESKTOP_ENTRY_LOCATION, out);
|
||||
}
|
||||
|
||||
if (pe.contains("HOME")) {
|
||||
fetchEntries(pe.value("HOME") + "/.local/share/applications", out);
|
||||
}
|
||||
|
||||
emit newAppList(out);
|
||||
}
|
||||
23
client/platforms/linux/linuxapplistprovider.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXAPPLISTPROVIDER_H
|
||||
#define LINUXAPPLISTPROVIDER_H
|
||||
|
||||
#include <applistprovider.h>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
|
||||
class LinuxAppListProvider final : public AppListProvider {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LinuxAppListProvider(QObject* parent);
|
||||
~LinuxAppListProvider();
|
||||
void getApplicationList() override;
|
||||
|
||||
private:
|
||||
void fetchEntries(const QString& dataDir, QMap<QString, QString>& map);
|
||||
};
|
||||
|
||||
#endif // LINUXAPPLISTPROVIDER_H
|
||||
213
client/platforms/linux/linuxcontroller.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxcontroller.h"
|
||||
#include "backendlogsobserver.h"
|
||||
#include "dbusclient.h"
|
||||
#include "errorhandler.h"
|
||||
#include "ipaddressrange.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "models/device.h"
|
||||
#include "models/keys.h"
|
||||
#include "models/server.h"
|
||||
#include "mozillavpn.h"
|
||||
|
||||
#include <QDBusPendingCallWatcher>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonValue>
|
||||
#include <QProcess>
|
||||
#include <QString>
|
||||
|
||||
namespace {
|
||||
Logger logger({LOG_LINUX, LOG_CONTROLLER}, "LinuxController");
|
||||
}
|
||||
|
||||
LinuxController::LinuxController() {
|
||||
MVPN_COUNT_CTOR(LinuxController);
|
||||
|
||||
m_dbus = new DBusClient(this);
|
||||
connect(m_dbus, &DBusClient::connected, this, &LinuxController::hopConnected);
|
||||
connect(m_dbus, &DBusClient::disconnected, this,
|
||||
&LinuxController::hopDisconnected);
|
||||
}
|
||||
|
||||
LinuxController::~LinuxController() { MVPN_COUNT_DTOR(LinuxController); }
|
||||
|
||||
void LinuxController::initialize(const Device* device, const Keys* keys) {
|
||||
Q_UNUSED(device);
|
||||
Q_UNUSED(keys);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = m_dbus->status();
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
||||
&LinuxController::initializeCompleted);
|
||||
}
|
||||
|
||||
void LinuxController::initializeCompleted(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<QString> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error received from the DBus service";
|
||||
emit initialized(false, false, QDateTime());
|
||||
return;
|
||||
}
|
||||
|
||||
QString status = reply.argumentAt<0>();
|
||||
logger.debug() << "Status:" << status;
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(status.toLocal8Bit());
|
||||
Q_ASSERT(json.isObject());
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
Q_ASSERT(obj.contains("status"));
|
||||
QJsonValue statusValue = obj.value("status");
|
||||
Q_ASSERT(statusValue.isBool());
|
||||
|
||||
emit initialized(true, statusValue.toBool(), QDateTime::currentDateTime());
|
||||
}
|
||||
|
||||
void LinuxController::activate(
|
||||
const QList<Server>& serverList, const Device* device, const Keys* keys,
|
||||
const QList<IPAddressRange>& allowedIPAddressRanges,
|
||||
const QList<QString>& vpnDisabledApps, const QHostAddress& dnsServer,
|
||||
Reason reason) {
|
||||
Q_UNUSED(reason);
|
||||
Q_UNUSED(vpnDisabledApps);
|
||||
|
||||
// Activate connections starting from the outermost tunnel
|
||||
for (int hopindex = serverList.count() - 1; hopindex > 0; hopindex--) {
|
||||
const Server& hop = serverList[hopindex];
|
||||
const Server& next = serverList[hopindex - 1];
|
||||
QList<IPAddressRange> hopAddressRanges = {
|
||||
IPAddressRange(next.ipv4AddrIn()), IPAddressRange(next.ipv6AddrIn())};
|
||||
logger.debug() << "LinuxController hopindex" << hopindex << "activated";
|
||||
connect(m_dbus->activate(hop, device, keys, hopindex, hopAddressRanges,
|
||||
QStringList(), QHostAddress(hop.ipv4Gateway())),
|
||||
&QDBusPendingCallWatcher::finished, this,
|
||||
&LinuxController::operationCompleted);
|
||||
}
|
||||
|
||||
// Activate the final hop last
|
||||
logger.debug() << "LinuxController activated";
|
||||
const Server& server = serverList[0];
|
||||
connect(m_dbus->activate(server, device, keys, 0, allowedIPAddressRanges,
|
||||
vpnDisabledApps, dnsServer),
|
||||
&QDBusPendingCallWatcher::finished, this,
|
||||
&LinuxController::operationCompleted);
|
||||
}
|
||||
|
||||
void LinuxController::deactivate(Reason reason) {
|
||||
logger.debug() << "LinuxController deactivated";
|
||||
|
||||
if (reason == ReasonSwitching) {
|
||||
logger.debug() << "No disconnect for quick server switching";
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
connect(m_dbus->deactivate(), &QDBusPendingCallWatcher::finished, this,
|
||||
&LinuxController::operationCompleted);
|
||||
}
|
||||
|
||||
void LinuxController::operationCompleted(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<bool> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error received from the DBus service";
|
||||
MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError);
|
||||
emit disconnected();
|
||||
return;
|
||||
}
|
||||
|
||||
bool status = reply.argumentAt<0>();
|
||||
if (status) {
|
||||
logger.debug() << "DBus service says: all good.";
|
||||
// we will receive the connected/disconnected() signal;
|
||||
return;
|
||||
}
|
||||
|
||||
logger.error() << "DBus service says: error.";
|
||||
MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError);
|
||||
emit disconnected();
|
||||
}
|
||||
|
||||
void LinuxController::hopConnected(int hopindex) {
|
||||
if (hopindex == 0) {
|
||||
logger.debug() << "LinuxController connected";
|
||||
emit connected();
|
||||
} else {
|
||||
logger.debug() << "LinuxController hopindex" << hopindex << "connected";
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxController::hopDisconnected(int hopindex) {
|
||||
if (hopindex == 0) {
|
||||
logger.debug() << "LinuxController disconnected";
|
||||
emit disconnected();
|
||||
} else {
|
||||
logger.debug() << "LinuxController hopindex" << hopindex << "disconnected";
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxController::checkStatus() {
|
||||
logger.debug() << "Check status";
|
||||
|
||||
QDBusPendingCallWatcher* watcher = m_dbus->status();
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
||||
&LinuxController::checkStatusCompleted);
|
||||
}
|
||||
|
||||
void LinuxController::checkStatusCompleted(QDBusPendingCallWatcher* call) {
|
||||
QDBusPendingReply<QString> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "Error received from the DBus service";
|
||||
return;
|
||||
}
|
||||
|
||||
QString status = reply.argumentAt<0>();
|
||||
logger.debug() << "Status:" << status;
|
||||
|
||||
QJsonDocument json = QJsonDocument::fromJson(status.toLocal8Bit());
|
||||
Q_ASSERT(json.isObject());
|
||||
|
||||
QJsonObject obj = json.object();
|
||||
Q_ASSERT(obj.contains("status"));
|
||||
QJsonValue statusValue = obj.value("status");
|
||||
Q_ASSERT(statusValue.isBool());
|
||||
|
||||
if (!statusValue.toBool()) {
|
||||
logger.error() << "Unable to retrieve the status from the interface.";
|
||||
return;
|
||||
}
|
||||
|
||||
Q_ASSERT(obj.contains("serverIpv4Gateway"));
|
||||
QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway");
|
||||
Q_ASSERT(serverIpv4Gateway.isString());
|
||||
|
||||
Q_ASSERT(obj.contains("deviceIpv4Address"));
|
||||
QJsonValue deviceIpv4Address = obj.value("deviceIpv4Address");
|
||||
Q_ASSERT(deviceIpv4Address.isString());
|
||||
|
||||
Q_ASSERT(obj.contains("txBytes"));
|
||||
QJsonValue txBytes = obj.value("txBytes");
|
||||
Q_ASSERT(txBytes.isDouble());
|
||||
|
||||
Q_ASSERT(obj.contains("rxBytes"));
|
||||
QJsonValue rxBytes = obj.value("rxBytes");
|
||||
Q_ASSERT(rxBytes.isDouble());
|
||||
|
||||
emit statusUpdated(serverIpv4Gateway.toString(), deviceIpv4Address.toString(),
|
||||
txBytes.toDouble(), rxBytes.toDouble());
|
||||
}
|
||||
|
||||
void LinuxController::getBackendLogs(
|
||||
std::function<void(const QString&)>&& a_callback) {
|
||||
std::function<void(const QString&)> callback = std::move(a_callback);
|
||||
|
||||
QDBusPendingCallWatcher* watcher = m_dbus->getLogs();
|
||||
connect(watcher, &QDBusPendingCallWatcher::finished,
|
||||
new BackendLogsObserver(this, std::move(callback)),
|
||||
&BackendLogsObserver::completed);
|
||||
}
|
||||
|
||||
void LinuxController::cleanupBackendLogs() { m_dbus->cleanupLogs(); }
|
||||
49
client/platforms/linux/linuxcontroller.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXCONTROLLER_H
|
||||
#define LINUXCONTROLLER_H
|
||||
|
||||
#include "controllerimpl.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class DBusClient;
|
||||
class QDBusPendingCallWatcher;
|
||||
|
||||
class LinuxController final : public ControllerImpl {
|
||||
Q_DISABLE_COPY_MOVE(LinuxController)
|
||||
|
||||
public:
|
||||
LinuxController();
|
||||
~LinuxController();
|
||||
|
||||
void initialize(const Device* device, const Keys* keys) override;
|
||||
|
||||
void activate(const QList<Server>& serverList, const Device* device,
|
||||
const Keys* keys,
|
||||
const QList<IPAddressRange>& allowedIPAddressRanges,
|
||||
const QList<QString>& vpnDisabledApps,
|
||||
const QHostAddress& dnsServer, Reason reason) override;
|
||||
|
||||
void deactivate(Reason reason) override;
|
||||
|
||||
void checkStatus() override;
|
||||
|
||||
void getBackendLogs(std::function<void(const QString&)>&& callback) override;
|
||||
|
||||
void cleanupBackendLogs() override;
|
||||
|
||||
private slots:
|
||||
void checkStatusCompleted(QDBusPendingCallWatcher* call);
|
||||
void initializeCompleted(QDBusPendingCallWatcher* call);
|
||||
void operationCompleted(QDBusPendingCallWatcher* call);
|
||||
void hopConnected(int hopindex);
|
||||
void hopDisconnected(int hopindex);
|
||||
|
||||
private:
|
||||
DBusClient* m_dbus = nullptr;
|
||||
};
|
||||
|
||||
#endif // LINUXCONTROLLER_H
|
||||
17
client/platforms/linux/linuxcryptosettings.cpp
Normal file
@@ -0,0 +1,17 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "cryptosettings.h"
|
||||
|
||||
void CryptoSettings::resetKey() {}
|
||||
|
||||
bool CryptoSettings::getKey(uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]) {
|
||||
Q_UNUSED(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
CryptoSettings::Version CryptoSettings::getSupportedVersion() {
|
||||
return CryptoSettings::NoEncryption;
|
||||
}
|
||||
127
client/platforms/linux/linuxdependencies.cpp
Normal file
@@ -0,0 +1,127 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxdependencies.h"
|
||||
#include "dbusclient.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <mntent.h>
|
||||
|
||||
constexpr const char* WG_QUICK = "wg-quick";
|
||||
|
||||
namespace {
|
||||
|
||||
Logger logger(LOG_LINUX, "LinuxDependencies");
|
||||
|
||||
void showAlert(const QString& message) {
|
||||
logger.debug() << "Show alert:" << message;
|
||||
|
||||
QMessageBox alert;
|
||||
alert.setText(message);
|
||||
alert.exec();
|
||||
}
|
||||
|
||||
bool findInPath(const char* what) {
|
||||
char* path = getenv("PATH");
|
||||
Q_ASSERT(path);
|
||||
|
||||
QStringList parts = QString(path).split(":");
|
||||
for (const QString& part : parts) {
|
||||
QDir pathDir(part);
|
||||
QFileInfo file(pathDir.filePath(what));
|
||||
if (file.exists()) {
|
||||
logger.debug() << what << "found" << file.filePath();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool checkDaemonVersion() {
|
||||
logger.debug() << "Check Daemon Version";
|
||||
|
||||
DBusClient* dbus = new DBusClient(nullptr);
|
||||
QDBusPendingCallWatcher* watcher = dbus->version();
|
||||
|
||||
bool completed = false;
|
||||
bool value = false;
|
||||
QObject::connect(
|
||||
watcher, &QDBusPendingCallWatcher::finished,
|
||||
[completed = &completed, value = &value](QDBusPendingCallWatcher* call) {
|
||||
*completed = true;
|
||||
|
||||
QDBusPendingReply<QString> reply = *call;
|
||||
if (reply.isError()) {
|
||||
logger.error() << "DBus message received - error";
|
||||
*value = false;
|
||||
return;
|
||||
}
|
||||
|
||||
QString version = reply.argumentAt<0>();
|
||||
*value = version == PROTOCOL_VERSION;
|
||||
|
||||
logger.debug() << "DBus message received - daemon version:" << version
|
||||
<< " - current version:" << PROTOCOL_VERSION;
|
||||
});
|
||||
|
||||
while (!completed) {
|
||||
QCoreApplication::processEvents();
|
||||
}
|
||||
|
||||
delete dbus;
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
bool LinuxDependencies::checkDependencies() {
|
||||
char* path = getenv("PATH");
|
||||
if (!path) {
|
||||
showAlert("No PATH env found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!findInPath(WG_QUICK)) {
|
||||
showAlert("Unable to locate wg-quick");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!checkDaemonVersion()) {
|
||||
showAlert("mozillavpn linuxdaemon needs to be updated or restarted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// static
|
||||
QString LinuxDependencies::findCgroupPath(const QString& type) {
|
||||
struct mntent entry;
|
||||
char buf[PATH_MAX];
|
||||
|
||||
FILE* fp = fopen("/etc/mtab", "r");
|
||||
if (fp == NULL) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
while (getmntent_r(fp, &entry, buf, sizeof(buf)) != NULL) {
|
||||
if (strcmp(entry.mnt_type, "cgroup") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (hasmntopt(&entry, type.toLocal8Bit().constData()) != NULL) {
|
||||
fclose(fp);
|
||||
return QString(entry.mnt_dir);
|
||||
}
|
||||
}
|
||||
fclose(fp);
|
||||
|
||||
return QString();
|
||||
}
|
||||
22
client/platforms/linux/linuxdependencies.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXDEPENDENCIES_H
|
||||
#define LINUXDEPENDENCIES_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class LinuxDependencies final {
|
||||
public:
|
||||
static bool checkDependencies();
|
||||
static QString findCgroupPath(const QString& type);
|
||||
|
||||
private:
|
||||
LinuxDependencies() = default;
|
||||
~LinuxDependencies() = default;
|
||||
|
||||
Q_DISABLE_COPY(LinuxDependencies)
|
||||
};
|
||||
|
||||
#endif // LINUXDEPENDENCIES_H
|
||||
55
client/platforms/linux/linuxnetworkwatcher.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxnetworkwatcher.h"
|
||||
#include "linuxnetworkwatcherworker.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "timersingleshot.h"
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "LinuxNetworkWatcher");
|
||||
}
|
||||
|
||||
LinuxNetworkWatcher::LinuxNetworkWatcher(QObject* parent)
|
||||
: NetworkWatcherImpl(parent) {
|
||||
MVPN_COUNT_CTOR(LinuxNetworkWatcher);
|
||||
|
||||
m_thread.start();
|
||||
}
|
||||
|
||||
LinuxNetworkWatcher::~LinuxNetworkWatcher() {
|
||||
MVPN_COUNT_DTOR(LinuxNetworkWatcher);
|
||||
|
||||
delete m_worker;
|
||||
|
||||
m_thread.quit();
|
||||
m_thread.wait();
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcher::initialize() {
|
||||
logger.debug() << "initialize";
|
||||
|
||||
m_worker = new LinuxNetworkWatcherWorker(&m_thread);
|
||||
|
||||
connect(this, &LinuxNetworkWatcher::checkDevicesInThread, m_worker,
|
||||
&LinuxNetworkWatcherWorker::checkDevices);
|
||||
|
||||
connect(m_worker, &LinuxNetworkWatcherWorker::unsecuredNetwork, this,
|
||||
&LinuxNetworkWatcher::unsecuredNetwork);
|
||||
|
||||
// Let's wait a few seconds to allow the UI to be fully loaded and shown.
|
||||
// This is not strictly needed, but it's better for user experience because
|
||||
// it makes the UI faster to appear, plus it gives a bit of delay between the
|
||||
// UI to appear and the first notification.
|
||||
TimerSingleShot::create(this, 2000, [this]() {
|
||||
QMetaObject::invokeMethod(m_worker, "initialize", Qt::QueuedConnection);
|
||||
});
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcher::start() {
|
||||
logger.debug() << "actived";
|
||||
NetworkWatcherImpl::start();
|
||||
emit checkDevicesInThread();
|
||||
}
|
||||
33
client/platforms/linux/linuxnetworkwatcher.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXNETWORKWATCHER_H
|
||||
#define LINUXNETWORKWATCHER_H
|
||||
|
||||
#include "networkwatcherimpl.h"
|
||||
|
||||
#include <QThread>
|
||||
|
||||
class LinuxNetworkWatcherWorker;
|
||||
|
||||
class LinuxNetworkWatcher final : public NetworkWatcherImpl {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LinuxNetworkWatcher(QObject* parent);
|
||||
~LinuxNetworkWatcher();
|
||||
|
||||
void initialize() override;
|
||||
|
||||
void start() override;
|
||||
|
||||
signals:
|
||||
void checkDevicesInThread();
|
||||
|
||||
private:
|
||||
LinuxNetworkWatcherWorker* m_worker = nullptr;
|
||||
QThread m_thread;
|
||||
};
|
||||
|
||||
#endif // LINUXNETWORKWATCHER_H
|
||||
175
client/platforms/linux/linuxnetworkwatcherworker.cpp
Normal file
@@ -0,0 +1,175 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxnetworkwatcherworker.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
|
||||
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType
|
||||
#ifndef NM_DEVICE_TYPE_WIFI
|
||||
# define NM_DEVICE_TYPE_WIFI 2
|
||||
#endif
|
||||
|
||||
// https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NM80211ApFlags
|
||||
// Wifi network has no security
|
||||
#ifndef NM_802_11_AP_SEC_NONE
|
||||
# define NM_802_11_AP_SEC_NONE 0x00000000
|
||||
#endif
|
||||
|
||||
// Wifi network has WEP (40 bits)
|
||||
#ifndef NM_802_11_AP_SEC_PAIR_WEP40
|
||||
# define NM_802_11_AP_SEC_PAIR_WEP40 0x00000001
|
||||
#endif
|
||||
|
||||
// Wifi network has WEP (104 bits)
|
||||
#ifndef NM_802_11_AP_SEC_PAIR_WEP104
|
||||
# define NM_802_11_AP_SEC_PAIR_WEP104 0x00000002
|
||||
#endif
|
||||
|
||||
#define NM_802_11_AP_SEC_WEAK_CRYPTO \
|
||||
(NM_802_11_AP_SEC_PAIR_WEP40 | NM_802_11_AP_SEC_PAIR_WEP104)
|
||||
|
||||
constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "LinuxNetworkWatcherWorker");
|
||||
}
|
||||
|
||||
static inline bool checkUnsecureFlags(int rsnFlags, int wpaFlags) {
|
||||
// If neither WPA nor WPA2/RSN are supported, then the network is unencrypted
|
||||
if (rsnFlags == NM_802_11_AP_SEC_NONE && wpaFlags == NM_802_11_AP_SEC_NONE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Consider the user of weak cryptography to be unsecure
|
||||
if ((rsnFlags & NM_802_11_AP_SEC_WEAK_CRYPTO) ||
|
||||
(wpaFlags & NM_802_11_AP_SEC_WEAK_CRYPTO)) {
|
||||
return false;
|
||||
}
|
||||
// Otherwise, the network is secured with reasonable cryptography
|
||||
return true;
|
||||
}
|
||||
|
||||
LinuxNetworkWatcherWorker::LinuxNetworkWatcherWorker(QThread* thread) {
|
||||
MVPN_COUNT_CTOR(LinuxNetworkWatcherWorker);
|
||||
moveToThread(thread);
|
||||
}
|
||||
|
||||
LinuxNetworkWatcherWorker::~LinuxNetworkWatcherWorker() {
|
||||
MVPN_COUNT_DTOR(LinuxNetworkWatcherWorker);
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::initialize() {
|
||||
logger.debug() << "initialize";
|
||||
|
||||
logger.debug()
|
||||
<< "Retrieving the list of wifi network devices from NetworkManager";
|
||||
|
||||
// To know the NeworkManager DBus methods and properties, read the official
|
||||
// documentation:
|
||||
// https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html
|
||||
|
||||
QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager",
|
||||
DBUS_NETWORKMANAGER, QDBusConnection::systemBus());
|
||||
if (!nm.isValid()) {
|
||||
logger.error()
|
||||
<< "Failed to connect to the network manager via system dbus";
|
||||
return;
|
||||
}
|
||||
|
||||
QDBusMessage msg = nm.call("GetDevices");
|
||||
QDBusArgument arg = msg.arguments().at(0).value<QDBusArgument>();
|
||||
if (arg.currentType() != QDBusArgument::ArrayType) {
|
||||
logger.error() << "Expected an array of devices";
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QDBusObjectPath> paths = qdbus_cast<QList<QDBusObjectPath> >(arg);
|
||||
for (const QDBusObjectPath& path : paths) {
|
||||
QString devicePath = path.path();
|
||||
QDBusInterface device(DBUS_NETWORKMANAGER, devicePath,
|
||||
"org.freedesktop.NetworkManager.Device",
|
||||
QDBusConnection::systemBus());
|
||||
if (device.property("DeviceType").toInt() != NM_DEVICE_TYPE_WIFI) {
|
||||
continue;
|
||||
}
|
||||
|
||||
logger.debug() << "Found a wifi device:" << devicePath;
|
||||
m_devicePaths.append(devicePath);
|
||||
|
||||
// Here we monitor the changes.
|
||||
QDBusConnection::systemBus().connect(
|
||||
DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.DBus.Properties",
|
||||
"PropertiesChanged", this,
|
||||
SLOT(propertyChanged(QString, QVariantMap, QStringList)));
|
||||
}
|
||||
|
||||
if (m_devicePaths.isEmpty()) {
|
||||
logger.warning() << "No wifi devices found";
|
||||
return;
|
||||
}
|
||||
|
||||
// We could be already be activated.
|
||||
checkDevices();
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::propertyChanged(QString interface,
|
||||
QVariantMap properties,
|
||||
QStringList list) {
|
||||
Q_UNUSED(list);
|
||||
|
||||
logger.debug() << "Properties changed for interface" << interface;
|
||||
|
||||
if (!properties.contains("ActiveAccessPoint")) {
|
||||
logger.debug() << "Access point did not changed. Ignoring the changes";
|
||||
return;
|
||||
}
|
||||
|
||||
checkDevices();
|
||||
}
|
||||
|
||||
void LinuxNetworkWatcherWorker::checkDevices() {
|
||||
logger.debug() << "Checking devices";
|
||||
|
||||
for (const QString& devicePath : m_devicePaths) {
|
||||
QDBusInterface wifiDevice(DBUS_NETWORKMANAGER, devicePath,
|
||||
"org.freedesktop.NetworkManager.Device.Wireless",
|
||||
QDBusConnection::systemBus());
|
||||
|
||||
// Check the access point path
|
||||
QString accessPointPath = wifiDevice.property("ActiveAccessPoint")
|
||||
.value<QDBusObjectPath>()
|
||||
.path();
|
||||
if (accessPointPath.isEmpty()) {
|
||||
logger.warning() << "No access point found";
|
||||
continue;
|
||||
}
|
||||
|
||||
QDBusInterface ap(DBUS_NETWORKMANAGER, accessPointPath,
|
||||
"org.freedesktop.NetworkManager.AccessPoint",
|
||||
QDBusConnection::systemBus());
|
||||
|
||||
QVariant rsnFlags = ap.property("RsnFlags");
|
||||
QVariant wpaFlags = ap.property("WpaFlags");
|
||||
if (!rsnFlags.isValid() || !wpaFlags.isValid()) {
|
||||
// We are probably not connected.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!checkUnsecureFlags(rsnFlags.toInt(), wpaFlags.toInt())) {
|
||||
QString ssid = ap.property("Ssid").toString();
|
||||
QString bssid = ap.property("HwAddress").toString();
|
||||
|
||||
// We have found 1 unsecured network. We don't need to check other wifi
|
||||
// network devices.
|
||||
logger.warning() << "Unsecured AP detected!"
|
||||
<< "rsnFlags:" << rsnFlags.toInt()
|
||||
<< "wpaFlags:" << wpaFlags.toInt() << "ssid:" << ssid;
|
||||
emit unsecuredNetwork(ssid, bssid);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
client/platforms/linux/linuxnetworkwatcherworker.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXNETWORKWATCHERWORKER_H
|
||||
#define LINUXNETWORKWATCHERWORKER_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
class QThread;
|
||||
|
||||
class LinuxNetworkWatcherWorker final : public QObject {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(LinuxNetworkWatcherWorker)
|
||||
|
||||
public:
|
||||
explicit LinuxNetworkWatcherWorker(QThread* thread);
|
||||
~LinuxNetworkWatcherWorker();
|
||||
|
||||
void checkDevices();
|
||||
|
||||
signals:
|
||||
void unsecuredNetwork(const QString& networkName, const QString& networkId);
|
||||
|
||||
public slots:
|
||||
void initialize();
|
||||
|
||||
private slots:
|
||||
void propertyChanged(QString interface, QVariantMap properties,
|
||||
QStringList list);
|
||||
|
||||
private:
|
||||
// We collect the list of DBus wifi network device paths during the
|
||||
// initialization. When a property of them changes, we check if the access
|
||||
// point is active and unsecure.
|
||||
QStringList m_devicePaths;
|
||||
};
|
||||
|
||||
#endif // LINUXNETWORKWATCHERWORKER_H
|
||||
191
client/platforms/linux/linuxpingsender.cpp
Normal file
@@ -0,0 +1,191 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "linuxpingsender.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
|
||||
#include <QSocketNotifier>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <errno.h>
|
||||
#include <linux/filter.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <netinet/ip_icmp.h>
|
||||
#include <sys/socket.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace {
|
||||
Logger logger({LOG_LINUX, LOG_NETWORKING}, "LinuxPingSender");
|
||||
}
|
||||
|
||||
int LinuxPingSender::createSocket() {
|
||||
// Try creating an ICMP socket. This would be the ideal choice, but it can
|
||||
// fail depending on the kernel config (see: sys.net.ipv4.ping_group_range)
|
||||
m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
|
||||
if (m_socket >= 0) {
|
||||
m_ident = 0;
|
||||
return m_socket;
|
||||
}
|
||||
if ((errno != EPERM) && (errno != EACCES)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// As a fallback, create a raw socket, which requires root permissions
|
||||
// or CAP_NET_RAW to be granted to the VPN client.
|
||||
m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
|
||||
if (m_socket < 0) {
|
||||
return -1;
|
||||
}
|
||||
m_ident = getpid() & 0xffff;
|
||||
|
||||
// Attach a BPF filter to discard everything but replies to our echo.
|
||||
struct sock_filter bpf_prog[] = {
|
||||
BPF_STMT(BPF_LDX | BPF_B | BPF_MSH, 0), /* Skip IP header. */
|
||||
BPF_STMT(BPF_LD | BPF_H | BPF_IND, 4), /* Load icmp echo ident */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, m_ident, 1, 0), /* Ours? */
|
||||
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected identifier. Reject. */
|
||||
BPF_STMT(BPF_LD | BPF_B | BPF_IND, 0), /* Load icmp type */
|
||||
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, ICMP_ECHOREPLY, 1, 0), /* Echo? */
|
||||
BPF_STMT(BPF_RET | BPF_K, 0), /* Unexpected type. Reject. */
|
||||
BPF_STMT(BPF_RET | BPF_K, ~0U), /* Packet passes the filter. */
|
||||
};
|
||||
struct sock_fprog filter = {
|
||||
.len = sizeof(bpf_prog) / sizeof(struct sock_filter),
|
||||
.filter = bpf_prog,
|
||||
};
|
||||
setsockopt(m_socket, SOL_SOCKET, SO_ATTACH_FILTER, &filter, sizeof(filter));
|
||||
|
||||
return m_socket;
|
||||
}
|
||||
|
||||
LinuxPingSender::LinuxPingSender(const QString& source, QObject* parent)
|
||||
: PingSender(parent), m_source(source) {
|
||||
MVPN_COUNT_CTOR(LinuxPingSender);
|
||||
logger.debug() << "LinuxPingSender(" + source + ") created";
|
||||
|
||||
m_socket = createSocket();
|
||||
if (m_socket < 0) {
|
||||
logger.error() << "Socket creation error: " << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof addr);
|
||||
addr.sin_family = AF_INET;
|
||||
if (inet_aton(source.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
|
||||
logger.error() << "source" << source << "error:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
if (bind(m_socket, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
|
||||
close(m_socket);
|
||||
m_socket = -1;
|
||||
logger.error() << "bind error:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
m_notifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this);
|
||||
if (m_ident) {
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&LinuxPingSender::rawSocketReady);
|
||||
} else {
|
||||
connect(m_notifier, &QSocketNotifier::activated, this,
|
||||
&LinuxPingSender::icmpSocketReady);
|
||||
}
|
||||
}
|
||||
|
||||
LinuxPingSender::~LinuxPingSender() {
|
||||
MVPN_COUNT_DTOR(LinuxPingSender);
|
||||
if (m_socket >= 0) {
|
||||
close(m_socket);
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxPingSender::sendPing(const QString& dest, quint16 sequence) {
|
||||
// QProcess is not supported on iOS. Because of this we cannot use the `ping`
|
||||
// app as fallback on this platform.
|
||||
#ifndef MVPN_IOS
|
||||
// Use the generic ping sender if we failed to open an ICMP socket.
|
||||
if (m_socket < 0) {
|
||||
QStringList args;
|
||||
args << "-c"
|
||||
<< "1";
|
||||
args << "-I" << m_source;
|
||||
args << dest;
|
||||
genericSendPing(args, sequence);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
struct sockaddr_in addr;
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
addr.sin_family = AF_INET;
|
||||
if (inet_aton(dest.toLocal8Bit().constData(), &addr.sin_addr) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct icmphdr packet;
|
||||
memset(&packet, 0, sizeof(packet));
|
||||
packet.type = ICMP_ECHO;
|
||||
packet.un.echo.id = htons(m_ident);
|
||||
packet.un.echo.sequence = htons(sequence);
|
||||
packet.checksum = inetChecksum(&packet, sizeof(packet));
|
||||
|
||||
int rc = sendto(m_socket, &packet, sizeof(packet), 0, (struct sockaddr*)&addr,
|
||||
sizeof(addr));
|
||||
if (rc < 0) {
|
||||
logger.error() << "failed to send:" << strerror(errno);
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxPingSender::icmpSocketReady() {
|
||||
socklen_t slen = 0;
|
||||
unsigned char data[2048];
|
||||
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
|
||||
if (rc <= 0) {
|
||||
logger.error() << "recvfrom failed:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
struct icmphdr packet;
|
||||
if (rc >= (int)sizeof(packet)) {
|
||||
memcpy(&packet, data, sizeof(packet));
|
||||
if (packet.type == ICMP_ECHOREPLY) {
|
||||
emit recvPing(htons(packet.un.echo.sequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LinuxPingSender::rawSocketReady() {
|
||||
socklen_t slen = 0;
|
||||
unsigned char data[2048];
|
||||
int rc = recvfrom(m_socket, data, sizeof(data), MSG_DONTWAIT, NULL, &slen);
|
||||
if (rc <= 0) {
|
||||
logger.error() << "recvfrom failed:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the IP header
|
||||
const struct iphdr* ip = (struct iphdr*)data;
|
||||
int iphdrlen = ip->ihl * 4;
|
||||
if (rc < iphdrlen || iphdrlen < (int)sizeof(struct iphdr)) {
|
||||
logger.error() << "malformed IP packet:" << strerror(errno);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check the ICMP packet
|
||||
struct icmphdr packet;
|
||||
if (inetChecksum(data + iphdrlen, rc - iphdrlen) != 0) {
|
||||
logger.warning() << "invalid checksum";
|
||||
return;
|
||||
}
|
||||
if (rc >= (iphdrlen + (int)sizeof(packet))) {
|
||||
memcpy(&packet, data + iphdrlen, sizeof(packet));
|
||||
quint16 id = htons(m_ident);
|
||||
if ((packet.type == ICMP_ECHOREPLY) && (packet.un.echo.id == id)) {
|
||||
emit recvPing(htons(packet.un.echo.sequence));
|
||||
}
|
||||
}
|
||||
}
|
||||
38
client/platforms/linux/linuxpingsender.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXPINGSENDER_H
|
||||
#define LINUXPINGSENDER_H
|
||||
|
||||
#include "pingsender.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QSocketNotifier;
|
||||
|
||||
class LinuxPingSender final : public PingSender {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(LinuxPingSender)
|
||||
|
||||
public:
|
||||
LinuxPingSender(const QString& source, QObject* parent = nullptr);
|
||||
~LinuxPingSender();
|
||||
|
||||
void sendPing(const QString& dest, quint16 sequence) override;
|
||||
|
||||
private:
|
||||
int createSocket();
|
||||
|
||||
private slots:
|
||||
void rawSocketReady();
|
||||
void icmpSocketReady();
|
||||
|
||||
private:
|
||||
QSocketNotifier* m_notifier = nullptr;
|
||||
QString m_source;
|
||||
int m_socket = 0;
|
||||
quint16 m_ident = 0;
|
||||
};
|
||||
|
||||
#endif // LINUXPINGSENDER_H
|
||||
161
client/platforms/linux/linuxsystemtraynotificationhandler.cpp
Normal file
@@ -0,0 +1,161 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "platforms/linux/linuxsystemtraynotificationhandler.h"
|
||||
#include "constants.h"
|
||||
#include "leakdetector.h"
|
||||
#include "logger.h"
|
||||
#include "defines.h"
|
||||
|
||||
#include <QtDBus/QtDBus>
|
||||
#include <QDesktopServices>
|
||||
|
||||
constexpr const char* DBUS_ITEM = "org.freedesktop.Notifications";
|
||||
constexpr const char* DBUS_PATH = "/org/freedesktop/Notifications";
|
||||
constexpr const char* DBUS_INTERFACE = "org.freedesktop.Notifications";
|
||||
constexpr const char* ACTION_ID = "mozilla_vpn_notification";
|
||||
|
||||
namespace {
|
||||
Logger logger(LOG_LINUX, "LinuxSystemTrayNotificationHandler");
|
||||
} // namespace
|
||||
|
||||
//static
|
||||
bool LinuxSystemTrayNotificationHandler::requiredCustomImpl() {
|
||||
//if (!QDBusConnection::sessionBus().isConnected()) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//QDBusConnectionInterface* interface =
|
||||
// QDBusConnection::sessionBus().interface();
|
||||
//if (!interface) {
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//// This custom systemTrayHandler implementation is required only on Unity.
|
||||
//QStringList registeredServices = interface->registeredServiceNames().value();
|
||||
//return registeredServices.contains("com.canonical.Unity");
|
||||
}
|
||||
|
||||
LinuxSystemTrayNotificationHandler::LinuxSystemTrayNotificationHandler(
|
||||
QObject* parent)
|
||||
: SystemTrayNotificationHandler(parent) {
|
||||
|
||||
m_systemTrayIcon.show();
|
||||
connect(&m_systemTrayIcon, &QSystemTrayIcon::activated, this, &LinuxSystemTrayNotificationHandler::onTrayActivated);
|
||||
|
||||
|
||||
m_menu.addAction(QIcon(":/images/tray/application.png"), tr("Show") + " " + APPLICATION_NAME, this, [this](){
|
||||
emit raiseRequested();
|
||||
});
|
||||
m_menu.addSeparator();
|
||||
m_trayActionConnect = m_menu.addAction(tr("Connect"), this, [this](){ emit connectRequested(); });
|
||||
m_trayActionDisconnect = m_menu.addAction(tr("Disconnect"), this, [this](){ emit disconnectRequested(); });
|
||||
|
||||
m_menu.addSeparator();
|
||||
|
||||
m_menu.addAction(QIcon(":/images/tray/link.png"), tr("Visit Website"), [&](){
|
||||
QDesktopServices::openUrl(QUrl("https://amnezia.org"));
|
||||
});
|
||||
|
||||
m_menu.addAction(QIcon(":/images/tray/cancel.png"), tr("Quit") + " " + APPLICATION_NAME, this, [&](){
|
||||
qApp->quit();
|
||||
});
|
||||
|
||||
m_systemTrayIcon.setContextMenu(&m_menu);
|
||||
setTrayState(VpnProtocol::Disconnected);
|
||||
}
|
||||
|
||||
void LinuxSystemTrayNotificationHandler::setTrayState(VpnProtocol::VpnConnectionState state)
|
||||
{
|
||||
qDebug() << "SystemTrayNotificationHandler::setTrayState" << state;
|
||||
QString resourcesPath = ":/images/tray/%1";
|
||||
|
||||
switch (state) {
|
||||
case VpnProtocol::Disconnected:
|
||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(true);
|
||||
m_trayActionDisconnect->setEnabled(false);
|
||||
break;
|
||||
case VpnProtocol::Preparing:
|
||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
break;
|
||||
case VpnProtocol::Connecting:
|
||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
break;
|
||||
case VpnProtocol::Connected:
|
||||
setTrayIcon(QString(resourcesPath).arg(ConnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
break;
|
||||
case VpnProtocol::Disconnecting:
|
||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
break;
|
||||
case VpnProtocol::Reconnecting:
|
||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
break;
|
||||
case VpnProtocol::Error:
|
||||
setTrayIcon(QString(resourcesPath).arg(ErrorTrayIconName));
|
||||
m_trayActionConnect->setEnabled(true);
|
||||
m_trayActionDisconnect->setEnabled(false);
|
||||
break;
|
||||
case VpnProtocol::Unknown:
|
||||
default:
|
||||
m_trayActionConnect->setEnabled(false);
|
||||
m_trayActionDisconnect->setEnabled(true);
|
||||
setTrayIcon(QString(resourcesPath).arg(DisconnectedTrayIconName));
|
||||
}
|
||||
|
||||
//#ifdef Q_OS_MAC
|
||||
// // Get theme from current user (note, this app can be launched as root application and in this case this theme can be different from theme of real current user )
|
||||
// bool darkTaskBar = MacOSFunctions::instance().isMenuBarUseDarkTheme();
|
||||
// darkTaskBar = forceUseBrightIcons ? true : darkTaskBar;
|
||||
// resourcesPath = ":/images_mac/tray_icon/%1";
|
||||
// useIconName = useIconName.replace(".png", darkTaskBar ? "@2x.png" : " dark@2x.png");
|
||||
//#endif
|
||||
}
|
||||
|
||||
void LinuxSystemTrayNotificationHandler::setTrayIcon(const QString &iconPath)
|
||||
{
|
||||
QIcon trayIconMask(QPixmap(iconPath).scaled(128,128));
|
||||
trayIconMask.setIsMask(true);
|
||||
m_systemTrayIcon.setIcon(trayIconMask);
|
||||
}
|
||||
|
||||
void LinuxSystemTrayNotificationHandler::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
|
||||
{
|
||||
#ifndef Q_OS_MAC
|
||||
if(reason == QSystemTrayIcon::DoubleClick || reason == QSystemTrayIcon::Trigger) {
|
||||
emit raiseRequested();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
LinuxSystemTrayNotificationHandler::~LinuxSystemTrayNotificationHandler() {
|
||||
MVPN_COUNT_DTOR(LinuxSystemTrayNotificationHandler);
|
||||
}
|
||||
|
||||
void LinuxSystemTrayNotificationHandler::notify(Message type,
|
||||
const QString& title,
|
||||
const QString& message,
|
||||
int timerMsec) {
|
||||
|
||||
|
||||
}
|
||||
|
||||
void LinuxSystemTrayNotificationHandler::actionInvoked(uint actionId,
|
||||
QString action) {
|
||||
logger.debug() << "Notification clicked" << actionId << action;
|
||||
|
||||
if (action == ACTION_ID && m_lastNotificationId == actionId) {
|
||||
messageClickHandle();
|
||||
}
|
||||
}
|
||||
49
client/platforms/linux/linuxsystemtraynotificationhandler.h
Normal file
@@ -0,0 +1,49 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef LINUXNOTIFICATIONNOTIFICATIONHANDLER_H
|
||||
#define LINUXNOTIFICATIONNOTIFICATIONHANDLER_H
|
||||
|
||||
#include "./ui/systemtray_notificationhandler.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class LinuxSystemTrayNotificationHandler final
|
||||
: public SystemTrayNotificationHandler {
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY_MOVE(LinuxSystemTrayNotificationHandler)
|
||||
|
||||
public:
|
||||
static bool requiredCustomImpl();
|
||||
|
||||
LinuxSystemTrayNotificationHandler(QObject* parent);
|
||||
~LinuxSystemTrayNotificationHandler();
|
||||
|
||||
private:
|
||||
void notify(Message type, const QString& title, const QString& message,
|
||||
int timerMsec) override;
|
||||
void setTrayState(VpnProtocol::VpnConnectionState state);
|
||||
void setTrayIcon(const QString &iconPath);
|
||||
void onTrayActivated(QSystemTrayIcon::ActivationReason reason);
|
||||
private slots:
|
||||
void actionInvoked(uint actionId, QString action);
|
||||
|
||||
private:
|
||||
QMenu m_menu;
|
||||
QSystemTrayIcon m_systemTrayIcon;
|
||||
|
||||
QAction* m_trayActionConnect = nullptr;
|
||||
QAction* m_trayActionDisconnect = nullptr;
|
||||
QAction* m_preferencesAction = nullptr;
|
||||
QAction* m_statusLabel = nullptr;
|
||||
QAction* m_separator = nullptr;
|
||||
|
||||
const QString ConnectedTrayIconName = "active.png";
|
||||
const QString DisconnectedTrayIconName = "default.png";
|
||||
const QString ErrorTrayIconName = "error.png";
|
||||
|
||||
uint m_lastNotificationId = 0;
|
||||
};
|
||||
|
||||
#endif // LINUXNOTIFICATIONNOTIFICATIONHANDLER_H
|
||||
@@ -159,8 +159,8 @@ ErrorCode OpenVpnProtocol::start()
|
||||
return lastError();
|
||||
}
|
||||
|
||||
QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log";
|
||||
Utils::createEmptyFile(vpnLogFileNamePath);
|
||||
// QString vpnLogFileNamePath = Utils::systemLogPath() + "/openvpn.log";
|
||||
// Utils::createEmptyFile(vpnLogFileNamePath);
|
||||
|
||||
if (!m_managementServer.start(m_managementHost, m_managementPort)) {
|
||||
setLastError(ErrorCode::OpenVpnManagementServerError);
|
||||
@@ -186,8 +186,7 @@ ErrorCode OpenVpnProtocol::start()
|
||||
m_openVpnProcess->setProgram(openVpnExecPath());
|
||||
QStringList arguments({"--config" , configPath(),
|
||||
"--management", m_managementHost, QString::number(m_managementPort),
|
||||
"--management-client",
|
||||
"--log", vpnLogFileNamePath
|
||||
"--management-client"/*, "--log", vpnLogFileNamePath */
|
||||
});
|
||||
m_openVpnProcess->setArguments(arguments);
|
||||
|
||||
|
||||
@@ -15,6 +15,10 @@ constexpr char password[] = "password";
|
||||
constexpr char port[] = "port";
|
||||
constexpr char local_port[] = "local_port";
|
||||
|
||||
constexpr char dns1[] = "dns1";
|
||||
constexpr char dns2[] = "dns2";
|
||||
|
||||
|
||||
constexpr char description[] = "description";
|
||||
constexpr char cert[] = "cert";
|
||||
constexpr char config[] = "config";
|
||||
@@ -55,6 +59,9 @@ constexpr char last_config[] = "last_config";
|
||||
|
||||
namespace protocols {
|
||||
|
||||
namespace dns {
|
||||
constexpr char amneziaDnsIp[] = "172.29.172.254";
|
||||
}
|
||||
|
||||
namespace openvpn {
|
||||
constexpr char defaultSubnetAddress[] = "10.8.0.0";
|
||||
@@ -90,7 +97,7 @@ constexpr char ckBypassUidKeyPath[] = "/opt/amnezia/cloak/cloak_bypass_uid.key";
|
||||
constexpr char ckAdminKeyPath[] = "/opt/amnezia/cloak/cloak_admin_uid.key";
|
||||
constexpr char defaultPort[] = "443";
|
||||
constexpr char defaultRedirSite[] = "tile.openstreetmap.org";
|
||||
constexpr char defaultCipher[] = "chacha20-ietf-poly1305";
|
||||
constexpr char defaultCipher[] = "chacha20-poly1305";
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -103,12 +103,14 @@ QString VpnProtocol::vpnGateway() const
|
||||
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject& configuration)
|
||||
{
|
||||
switch (container) {
|
||||
#if defined(Q_OS_WINDOWS)
|
||||
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
|
||||
#endif
|
||||
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))
|
||||
case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration);
|
||||
case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration);
|
||||
case DockerContainer::ShadowSocks: return new ShadowSocksVpnProtocol(configuration);
|
||||
case DockerContainer::WireGuard: return new WireguardProtocol(configuration);
|
||||
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
|
||||
#endif
|
||||
default: return nullptr;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,9 @@
|
||||
<file>fonts/Lato-Thin.ttf</file>
|
||||
<file>fonts/Lato-ThinItalic.ttf</file>
|
||||
<file>images/AmneziaVPN.png</file>
|
||||
<file>images/server_settings.png</file>
|
||||
<file>images/share.png</file>
|
||||
<file>server_scripts/remove_container.sh</file>
|
||||
<file>server_scripts/setup_host_firewall.sh</file>
|
||||
<file>images/reload.png</file>
|
||||
<file>server_scripts/openvpn_cloak/Dockerfile</file>
|
||||
<file>server_scripts/openvpn_cloak/configure_container.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/start.sh</file>
|
||||
@@ -42,7 +40,6 @@
|
||||
<file>images/check.png</file>
|
||||
<file>images/uncheck.png</file>
|
||||
<file>images/settings_grey.png</file>
|
||||
<file>images/plus.png</file>
|
||||
<file>server_scripts/check_connection.sh</file>
|
||||
<file>server_scripts/remove_all_containers.sh</file>
|
||||
<file>server_scripts/openvpn_cloak/run_container.sh</file>
|
||||
@@ -142,5 +139,23 @@
|
||||
<file>images/connected.png</file>
|
||||
<file>images/disconnected.png</file>
|
||||
<file>ui/qml/Pages/PageQrDecoder.qml</file>
|
||||
<file>ui/qml/Pages/PageAbout.qml</file>
|
||||
<file>ui/qml/Controls/RichLabelType.qml</file>
|
||||
<file>images/svg/gpp_good_black_24dp.svg</file>
|
||||
<file>ui/qml/Controls/SvgImageType.qml</file>
|
||||
<file>images/svg/gpp_maybe_black_24dp.svg</file>
|
||||
<file>images/svg/close_black_24dp.svg</file>
|
||||
<file>images/svg/delete_black_24dp.svg</file>
|
||||
<file>images/svg/done_black_24dp.svg</file>
|
||||
<file>images/svg/format_list_bulleted_black_24dp.svg</file>
|
||||
<file>images/svg/logout_black_24dp.svg</file>
|
||||
<file>images/svg/miscellaneous_services_black_24dp.svg</file>
|
||||
<file>images/svg/refresh_black_24dp.svg</file>
|
||||
<file>images/svg/settings_black_24dp.svg</file>
|
||||
<file>images/svg/share_black_24dp.svg</file>
|
||||
<file>images/svg/vpn_key_black_24dp.svg</file>
|
||||
<file>images/svg/control_point_black_24dp.svg</file>
|
||||
<file>images/svg/settings_suggest_black_24dp.svg</file>
|
||||
<file>ui/qml/Controls/SvgButtonType.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p 53:53/udp -p 53:53/tcp --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
sudo docker run -d --restart always --network amnezia-dns-net --ip=172.29.172.254 --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
@@ -5,3 +5,4 @@ sudo docker run \
|
||||
-d --privileged \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
sudo docker run \
|
||||
-d --restart always \
|
||||
--cap-add=NET_ADMIN \
|
||||
-p $OPENVPN_PORT:$OPENVPN_PORT/$OPENVPN_TRANSPORT_PROTO \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
@@ -14,9 +14,12 @@ iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i tun0 -o eth0 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i tun0 -o eth1 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
|
||||
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL openvpn
|
||||
|
||||
@@ -2,6 +2,10 @@ FROM alpine:latest
|
||||
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
ARG SS_RELEASE="v1.13.1"
|
||||
ARG CLOAK_RELEASE="v2.5.5"
|
||||
ARG SERVER_ARCH
|
||||
|
||||
#Install required packages
|
||||
RUN apk add --no-cache curl openvpn easy-rsa bash netcat-openbsd dumb-init rng-tools
|
||||
RUN apk --update upgrade --no-cache
|
||||
@@ -13,10 +17,16 @@ RUN mkdir -p /opt/amnezia
|
||||
RUN echo -e "#!/bin/bash\ntail -f /dev/null" > /opt/amnezia/start.sh
|
||||
RUN chmod a+x /opt/amnezia/start.sh
|
||||
|
||||
RUN curl -L https://github.com/cbeuw/Cloak/releases/download/v2.5.3/ck-server-linux-amd64-v2.5.3 > /usr/bin/ck-server
|
||||
RUN if [ $SERVER_ARCH="x86_64" ]; then CK_ARCH="amd64"; \
|
||||
elif [ $SERVER_ARCH="i686" ]; then CK_ARCH="386"; \
|
||||
elif [ $SERVER_ARCH="aarch64" ]; then CK_ARCH="arm64"; \
|
||||
elif [ $SERVER_ARCH="arm" ]; then CK_ARCH="arm"; \
|
||||
else exit -1; fi && \
|
||||
curl -L https://github.com/cbeuw/Cloak/releases/download/${CLOAK_RELEASE}/ck-server-linux-${CK_ARCH}-${CLOAK_RELEASE} > /usr/bin/ck-server
|
||||
RUN chmod a+x /usr/bin/ck-server
|
||||
|
||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/v1.10.9/shadowsocks-v1.10.9.x86_64-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz
|
||||
RUN curl -L https://github.com/shadowsocks/shadowsocks-rust/releases/download/${SS_RELEASE}/shadowsocks-${SS_RELEASE}.${SERVER_ARCH}-unknown-linux-musl.tar.xz > /usr/bin/ss.tar.xz
|
||||
|
||||
RUN tar -Jxvf /usr/bin/ss.tar.xz -C /usr/bin/
|
||||
RUN chmod a+x /usr/bin/ssserver
|
||||
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $CLOAK_SERVER_PORT:443/tcp --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
sudo docker run \
|
||||
-d --restart always \
|
||||
--cap-add=NET_ADMIN \
|
||||
-p $CLOAK_SERVER_PORT:443/tcp \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
@@ -14,9 +14,12 @@ iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i tun0 -o eth0 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i tun0 -o eth1 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
|
||||
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL openvpn
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="AmneziaVPN"
|
||||
|
||||
ARG SS_RELEASE="v1.11.2"
|
||||
ARG SS_RELEASE="v1.13.1"
|
||||
ARG SERVER_ARCH
|
||||
|
||||
#Install required packages
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
# Run container
|
||||
sudo docker run -d --restart always --cap-add=NET_ADMIN -p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp --name $CONTAINER_NAME $CONTAINER_NAME
|
||||
sudo docker run \
|
||||
-d --restart always \
|
||||
--cap-add=NET_ADMIN \
|
||||
-p $SHADOWSOCKS_SERVER_PORT:$SHADOWSOCKS_SERVER_PORT/tcp \
|
||||
--name $CONTAINER_NAME $CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
# Create tun device if not exist
|
||||
sudo docker exec -i $CONTAINER_NAME bash -c 'mkdir -p /dev/net; if [ ! -c /dev/net/tun ]; then mknod /dev/net/tun c 10 200; fi'
|
||||
|
||||
@@ -14,9 +14,12 @@ iptables -A OUTPUT -o tun0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i tun0 -o eth0 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i tun0 -o eth1 -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -j ACCEPT
|
||||
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $OPENVPN_SUBNET_IP/$OPENVPN_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||
|
||||
# kill daemons in case of restart
|
||||
killall -KILL openvpn
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
CUR_USER=$(whoami);\
|
||||
sudo mkdir -p $DOCKERFILE_FOLDER;\
|
||||
sudo chown $CUR_USER $DOCKERFILE_FOLDER
|
||||
if ! sudo docker network ls | grep -q amnezia-dns-net; then sudo docker network create --driver bridge --subnet=172.29.172.0/24 --opt com.docker.network.bridge.name=amn0 amnezia-dns-net; fi
|
||||
|
||||
@@ -10,6 +10,8 @@ sudo docker run -d \
|
||||
--name $CONTAINER_NAME \
|
||||
$CONTAINER_NAME
|
||||
|
||||
sudo docker network connect amnezia-dns-net $CONTAINER_NAME
|
||||
|
||||
# Prevent to route packets outside of the container in case if server behind of the NAT
|
||||
#sudo docker exec -i $CONTAINER_NAME sh -c "ifconfig eth0:0 $SERVER_IP_ADDRESS netmask 255.255.255.255 up"
|
||||
|
||||
|
||||
@@ -18,8 +18,11 @@ iptables -A OUTPUT -o wg0 -j ACCEPT
|
||||
|
||||
# Allow forwarding traffic only from the VPN.
|
||||
iptables -A FORWARD -i wg0 -o eth0 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
iptables -A FORWARD -i wg0 -o eth1 -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -j ACCEPT
|
||||
|
||||
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth0 -j MASQUERADE
|
||||
iptables -t nat -A POSTROUTING -s $WIREGUARD_SUBNET_IP/$WIREGUARD_SUBNET_CIDR -o eth1 -j MASQUERADE
|
||||
|
||||
tail -f /dev/null
|
||||
|
||||
@@ -228,6 +228,21 @@ void Settings::addVpnSite(RouteMode mode, const QString &site, const QString &ip
|
||||
setVpnSites(mode, sites);
|
||||
}
|
||||
|
||||
void Settings::addVpnSites(RouteMode mode, const QMap<QString, QString> &sites)
|
||||
{
|
||||
QVariantMap allSites = vpnSites(mode);
|
||||
for (auto i = sites.constBegin(); i != sites.constEnd(); ++i) {
|
||||
const QString &site = i.key();
|
||||
const QString &ip = i.value();
|
||||
|
||||
if (allSites.contains(site) && allSites.value(site) == ip) continue;
|
||||
|
||||
allSites.insert(site, ip);
|
||||
}
|
||||
|
||||
setVpnSites(mode, allSites);
|
||||
}
|
||||
|
||||
QStringList Settings::getVpnIps(RouteMode mode) const
|
||||
{
|
||||
QStringList ips;
|
||||
|
||||
@@ -67,6 +67,9 @@ public:
|
||||
bool isStartMinimized() const { return m_settings.value("Conf/startMinimized", false).toBool(); }
|
||||
void setStartMinimized(bool enabled) { m_settings.setValue("Conf/startMinimized", enabled); }
|
||||
|
||||
bool isSaveLogs() const { return m_settings.value("Conf/saveLogs", false).toBool(); }
|
||||
void setSaveLogs(bool enabled) { m_settings.setValue("Conf/saveLogs", enabled); }
|
||||
|
||||
enum RouteMode {
|
||||
VpnAllSites,
|
||||
VpnOnlyForwardSites,
|
||||
@@ -82,12 +85,15 @@ public:
|
||||
QVariantMap vpnSites(RouteMode mode) const { return m_settings.value("Conf/" + routeModeString(mode)).toMap(); }
|
||||
void setVpnSites(RouteMode mode, const QVariantMap &sites) { m_settings.setValue("Conf/"+ routeModeString(mode), sites); m_settings.sync(); }
|
||||
void addVpnSite(RouteMode mode, const QString &site, const QString &ip= "");
|
||||
void addVpnSites(RouteMode mode, const QMap<QString, QString> &sites); // map <site, ip>
|
||||
QStringList getVpnIps(RouteMode mode) const;
|
||||
void removeVpnSite(RouteMode mode, const QString &site);
|
||||
|
||||
void addVpnIps(RouteMode mode, const QStringList &ip);
|
||||
void removeVpnSites(RouteMode mode, const QStringList &sites);
|
||||
|
||||
bool useAmneziaDns() const { return m_settings.value("Conf/useAmneziaDns", true).toBool(); }
|
||||
void setUseAmneziaDns(bool enabled) { m_settings.setValue("Conf/useAmneziaDns", enabled); }
|
||||
|
||||
QString primaryDns() const;
|
||||
QString secondaryDns() const;
|
||||
|
||||