Compare commits

..
28 Commits
Author SHA1 Message Date
leetthewire c19f34570d first build 2022-04-14 16:41:39 -07:00
leetthewire e9f44ffcc6 maked first build 2022-04-02 08:02:27 -07:00
leetthewire a261ab4f0c editing linux sources 2022-04-02 07:31:54 -07:00
leetthewire 39de79d3cd started to file updating for linux build 2022-03-25 12:32:36 -07:00
leetthewire 40ab540179 Merge branch 'dev' of https://github.com/amnezia-vpn/desktop-client into linux_v2 2022-03-22 03:41:29 -07:00
leetthewire 7d7b6f4475 prepared to build 2022-03-22 03:40:47 -07:00
pokamest 912051637a Android manifest fix, IPC refactoring 2022-02-22 02:08:57 +03:00
pokamest c233f767f4 Multiple ui fixes, save file function reimpl 2022-02-15 17:08:55 +03:00
aman b63bf2465f Android service issue fixed - VPN is keep running when connected and ui closed 2022-02-13 22:52:04 +05:30
pokamest 505c9c6218 Various fixes 2022-02-09 15:23:20 +03:00
pokamest cb21991efa DNS container fix, Cloak binaries updated 2022-02-05 18:02:49 +03:00
pokamest 49e58c25f2 Installer version bump 2022-02-05 16:08:53 +03:00
pokamest ac6000a2ae Svg icons, dns ui fix 2022-02-05 15:52:14 +03:00
pokamest 6d1c9edc24 Log panel added 2022-02-04 17:49:48 +03:00
pokamest 1a7fa3746d DNS service fixes for all containers 2022-02-02 02:12:29 +03:00
pokamest f7a57ffeb4 Cloak container arm support, ss updated 2022-02-02 01:52:37 +03:00
pokamest ad9d45a154 Dns selection implemented 2022-02-01 19:48:59 +03:00
pokamest 5fffb2afe3 Dns container to internal network 2022-01-31 16:08:22 +03:00
pokamest 1269114074 Cleanup berfore uninstall, post-uninstall refactoring 2022-01-31 00:10:51 +03:00
pokamest d24f6ae064 Logs functions fixes 2022-01-30 17:35:57 +03:00
pokamest 95fe09489c Vpn and wizard pages fixes 2022-01-28 16:03:21 +03:00
pokamest 1a144da36d SFTP fixes for Macos 2022-01-24 14:29:37 -08:00
pokamest 8e26da1759 Macos build fixes 2022-01-23 15:25:53 -08:00
pokamest daf53226c3 SFTP fixes 2022-01-24 02:01:56 +03:00
pokamest 2b9e615e51 Various fixes 2022-01-23 19:16:40 +03:00
pokamest 02acbecef5 Re-resolve sites after VPN Connected 2022-01-22 20:00:06 +03:00
pokamest 8f28964ce2 Tiny ui fixes 2022-01-22 18:19:57 +03:00
pokamest 495e74e2f0 Sites page fix 2022-01-22 18:19:38 +03:00
187 changed files with 6416 additions and 5359 deletions

No files matched your search

+1 -1
View File
@@ -2,5 +2,5 @@ TEMPLATE = subdirs
SUBDIRS = client
!ios:!android {
SUBDIRS += service platform
SUBDIRS += service
}
+2 -2
View File
@@ -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);
+21 -42
View File
@@ -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>
+2 -2
View File
@@ -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 {
+301 -292
View File
@@ -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?
}
}
+20 -8
View File
@@ -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
+44 -8
View File
@@ -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;
}
+5 -2
View File
@@ -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,
+7 -3
View File
@@ -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;
+1 -1
View File
@@ -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);
};
-4
View File
@@ -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
}
-2
View File
@@ -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
-6
View File
@@ -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
+6
View File
@@ -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;
+1
View File
@@ -5,6 +5,7 @@
#include <QObject>
#include "sshconnection.h"
#include "sshremoteprocess.h"
#include "debug.h"
#include "defs.h"
#include "settings.h"
+88 -5
View File
@@ -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();
}
+23 -1
View File
@@ -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;
+2 -2
View 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
Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 454 B

+1
View 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

@@ -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
View 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

@@ -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
View 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

@@ -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

@@ -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

@@ -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
View 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

@@ -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
View 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

@@ -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

@@ -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
View 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
View 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

+40 -25
View File
@@ -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();
}
@@ -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);
}
@@ -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
@@ -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);
}
}
@@ -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
@@ -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();
}
@@ -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
@@ -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
@@ -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;
@@ -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
@@ -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;
}
@@ -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
@@ -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;
@@ -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
@@ -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>
@@ -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>
@@ -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;
}
@@ -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
@@ -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);
}
@@ -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
@@ -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);
}
}
@@ -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
View 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
View 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
File renamed without changes.
File renamed without changes.
@@ -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();
}
@@ -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
@@ -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);
}
@@ -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
View 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
View 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
@@ -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;
}
@@ -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();
}
@@ -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
@@ -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();
}
@@ -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
@@ -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;
}
}
}
@@ -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
View 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
View 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
@@ -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();
}
}
@@ -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
+3 -4
View File
@@ -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);
+8 -1
View File
@@ -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";
}
+3 -1
View File
@@ -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;
}
+18 -3
View File
@@ -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 -1
View File
@@ -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'
+3
View File
@@ -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
+12 -2
View File
@@ -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
View File
@@ -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"
+3
View File
@@ -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
+15
View File
@@ -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;
+6
View File
@@ -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;
File diff suppressed because it is too large. Load diff
Loaded 100 of 187 files, more files were not shown because too many files have changed in this diff. Show more