diff --git a/CHANGELOG.md b/CHANGELOG.md
index 387e27d..792e5bb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
##Changelog
+version 0.6.2:
+- [change] api-url for ProtonVPN updated - the one introduced in last update was out of date
+- [change] added support for Windscribe's stealth feature (OpenVPN over SSL)
+- [change] postrm functions added to deb/rpm/aur packages
+- [change] automatic reconnections for double hop if first hop fails/disconnects
+- [change] adjusted OpenVPN configs of Mullvad and Windscribe to match official ones
+- [bugfix] tray icon not always updated after establishing double hop connection
+- [bugfix] qomui crashes while performing latency checks when server(s) are deleted
+
version 0.6.1:
- [new] support for Windscribe
- [new] support for ProtonVPN
diff --git a/README.md b/README.md
index 66a2e37..cb37509 100755
--- a/README.md
+++ b/README.md
@@ -19,7 +19,7 @@ Qomui (Qt OpenVPN Management UI) is an easy-to-use OpenVPN Gui for GNU/Linux wit
- command-line interface
### Screenshots
-Taken on Arch/Plasma Arc Dark Theme - Qomui will adapt to your theme
+Screenshots were taken on Arch Linux/Plasma Arc Dark Theme - Qomui will adapt to your theme.
@@ -38,11 +38,11 @@ Taken on Arch/Plasma Arc Dark Theme - Qomui will adapt to your theme
#### Ubuntu
-Download and install [DEB-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.1/qomui-0.6.1-amd64.deb)
+Download and install [DEB-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.2/qomui-0.6.2-amd64.deb)
#### Fedora
-Download and install [RPM-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.1/qomui-0.6.1-1.x86_64.rpm)
+Download and install [RPM-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.2/qomui-0.6.2-1.x86_64.rpm)
#### Arch
@@ -111,6 +111,15 @@ qomui-cli --help
Qomui has been my first ever programming experience and a practical challenge for myself to learn a bit of Python. Hence, I'm aware that there is a lot of code that could probably be improved, streamlined and made more beautiful. I might have made some horrible mistakes, too. I'd appreciate any feedback as well as suggestions for new features.
### Changelog
+version 0.6.2:
+- [change] api-url for ProtonVPN updated - the one introduced in last update was out of date
+- [change] added support for Windscribe's stealth feature (OpenVPN over SSL)
+- [change] postrm functions added to deb/rpm/aur packages
+- [change] automatic reconnections for double hop if first hop fails/disconnects
+- [change] adjusted OpenVPN configs of Mullvad and Windscribe to match official ones
+- [bugfix] tray icon not always updated after establishing double hop connection
+- [bugfix] qomui crashes while performing latency checks when server(s) are deleted
+
version 0.6.1:
- [new] support for Windscribe
- [new] support for ProtonVPN
@@ -122,13 +131,3 @@ version 0.6.1:
- [bugfix] configs are not imported if url cannot be resolved
- [bugfix] old connection not killed after network change detected (in rare cases)
-version 0.6.0:
-- [new] support for Wireguard
-- [new] cli-interface
-- [change] additional parameters parsed from .desktop-files
-- [change] update routine now uses dpkg/rpm if installed as DEB/RPM package - reinstall required!
-- [bugfix] crashes at start when system tray not available
-- [bugfix] Info for active connection sometimes not updated correctly
-- [bugfix] Doublehop fails on Fedora
-
-
diff --git a/VERSION b/VERSION
index 7ceb040..b1d7abc 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.6.1
\ No newline at end of file
+0.6.2
\ No newline at end of file
diff --git a/qomui/qomui_gui.py b/qomui/qomui_gui.py
index 09cad1e..9dd6b57 100755
--- a/qomui/qomui_gui.py
+++ b/qomui/qomui_gui.py
@@ -106,6 +106,7 @@ class QomuiGui(QtWidgets.QWidget):
firewall_rules_changed = False
hop_active = 0
hop_log_monitor = 0
+ tun_hop = None
hop_server_dict = None
bypass_dict = {}
config_dict = {}
@@ -1139,23 +1140,23 @@ def networkstate(self, networkstate):
def providerChosen(self):
provider = self.addProviderBox.currentText()
- if provider == "Airvpn" or provider == "PIA" or provider == "Windscribe" or provider == "ProtonVPN":
+
+ p_txt = {"Airvpn" : ("Username", "Password"),
+ "PIA" : ("Username", "Password"),
+ "Windscribe" : ("Username", "Password"),
+ "Mullvad" : ("Account Numner", "N.A. - Leave empty"),
+ "ProtonVPN" : ("OpenVPN username", "OpenVPN password")
+ }
+
+ if provider in SUPPORTED_PROVIDERS:
self.addProviderEdit.setVisible(False)
- self.addProviderUserEdit.setPlaceholderText(_translate("Form", "Username", None))
- self.addProviderPassEdit.setPlaceholderText(_translate("Form", "Password", None))
+ self.addProviderUserEdit.setPlaceholderText(_translate("Form", p_txt[provider][0], None))
+ self.addProviderPassEdit.setPlaceholderText(_translate("Form", p_txt[provider][1], None))
if provider in self.provider_list:
self.addProviderDownloadBt.setText(_translate("Form", "Update", None))
else:
self.addProviderDownloadBt.setText(_translate("Form", "Download", None))
- elif provider == "Mullvad":
- self.addProviderEdit.setVisible(False)
- self.addProviderUserEdit.setPlaceholderText(_translate("Form", "Account Number", None))
- self.addProviderPassEdit.setPlaceholderText(_translate("Form", "N.A.", None))
- if provider in self.provider_list:
- self.addProviderDownloadBt.setText(_translate("Form", "Update", None))
- else:
- self.addProviderDownloadBt.setText(_translate("Form", "Download", None))
else:
self.addProviderEdit.setVisible(True)
self.addProviderEdit.setPlaceholderText(_translate("Form",
@@ -1458,18 +1459,21 @@ def display_latency(self, result):
server = result[0]
latency_string = result[1]
latency_float = result[2]
- old_index = self.index_list.index(server)
- bisect.insort(self.latency_list, latency_float)
- update_index = self.latency_list.index(latency_float)
- rm = self.index_list.index(server)
- self.index_list.pop(rm)
- self.index_list.insert(update_index, server)
- if getattr(self, server).isHidden() == True:
- hidden = True
- self.serverListWidget.takeItem(old_index)
- self.add_server_widget(server, self.server_dict[server], insert=update_index)
- self.serverListWidget.setRowHidden(update_index, hidden)
- getattr(self, server).display_latency(latency_string)
+ try:
+ old_index = self.index_list.index(server)
+ bisect.insort(self.latency_list, latency_float)
+ update_index = self.latency_list.index(latency_float)
+ rm = self.index_list.index(server)
+ self.index_list.pop(rm)
+ self.index_list.insert(update_index, server)
+ if getattr(self, server).isHidden() == True:
+ hidden = True
+ self.serverListWidget.takeItem(old_index)
+ self.add_server_widget(server, self.server_dict[server], insert=update_index)
+ self.serverListWidget.setRowHidden(update_index, hidden)
+ getattr(self, server).display_latency(latency_string)
+ except ValueError:
+ pass
def show_favourite_servers(self, state):
self.randomSeverBt.setVisible(True)
@@ -1748,7 +1752,6 @@ def openvpn_log_monitor(self, reply):
self.status = "active"
self.hop_log_monitor = 0
self.WaitBar.setVisible(False)
- self.show_active_connection(self.ovpn_dict, self.hop_server_dict)
try:
if self.config_dict["simpletray"] == 0:
self.trayIcon = QtGui.QIcon('%s/flags/%s.png' % (ROOTDIR,
@@ -1759,10 +1762,14 @@ def openvpn_log_monitor(self, reply):
except KeyError:
self.trayIcon = QtGui.QIcon('%s/flags/%s.png' % (ROOTDIR,
self.ovpn_dict["country"]
- ))
- self.tray.setIcon(QtGui.QIcon(self.trayIcon))
+ ))
+ finally:
+ self.tray.setIcon(QtGui.QIcon(self.trayIcon))
+ self.show_active_connection(self.ovpn_dict, self.hop_server_dict, tun_hop=self.tun_hop)
+ self.tun_hop = None
elif self.hop_active == 2 and self.hop_log_monitor != 1:
+ self.tun_hop = self.qomui_service.return_tun_device("hop")
self.hop_log_monitor = 1
with open('%s/last_server.json' % (HOMEDIR), 'w') as lserver:
@@ -1804,12 +1811,12 @@ def show_failmsg(self, text, information):
self.failmsg.setWindowModality(QtCore.Qt.WindowModal)
self.failmsg.show()
- def show_active_connection(self, current_server, hop_dict):
+ def show_active_connection(self, current_server, hop_dict, tun_hop=None):
self.tray.setToolTip("Connected to %s" %self.ovpn_dict["name"])
QtWidgets.QApplication.restoreOverrideCursor()
- tun = self.qomui_service.return_tun_device()
+ tun = self.qomui_service.return_tun_device("tun")
self.ActiveWidget.setVisible(True)
- self.ActiveWidget.setText(self.ovpn_dict, self.hop_server_dict, tun)
+ self.ActiveWidget.setText(self.ovpn_dict, self.hop_server_dict, tun, tun_hop)
self.ActiveWidget.disconnect.connect(self.kill)
self.ActiveWidget.reconnect.connect(self.reconnect)
self.gridLayout.addWidget(self.ActiveWidget, 0, 0, 1, 3)
@@ -1961,24 +1968,28 @@ def apply_edit(self, modifications):
json.dump(self.server_dict, s)
if len(new_config) != 0:
- if provider in SUPPORTED_PROVIDERS:
- temp_file = "%s/temp/%s_config" %(HOMEDIR, provider)
- with open(temp_file, "w") as config_change:
- config_change.writelines(new_config)
- else:
- temp_file = "%s/temp/%s" %(HOMEDIR, val["path"].split("/")[1])
- if modifications["apply_all"] == 1:
- for k, v in self.server_dict.items():
- if v["provider"] == provider:
- path = "%s/temp/%s" %(HOMEDIR, v["path"].split("/")[1])
- with open(path, "w") as config_change:
- index = modifications["index"]
- rpl = new_config[index].split(" ")
- ip_insert = "%s %s %s" %(rpl[0], v["ip"], rpl[2])
- new_config[index] = ip_insert
- config_change.writelines(new_config)
-
- self.qomui_service.copy_rootdir("CHANGE_%s" %provider, "%s/temp" %(HOMEDIR))
+ try:
+ if provider in SUPPORTED_PROVIDERS:
+ temp_file = "%s/temp/%s_config" %(HOMEDIR, provider)
+ with open(temp_file, "w") as config_change:
+ config_change.writelines(new_config)
+ else:
+ temp_file = "%s/temp/%s" %(HOMEDIR, val["path"].split("/")[1])
+ if modifications["apply_all"] == 1:
+ for k, v in self.server_dict.items():
+ if v["provider"] == provider:
+ path = "%s/temp/%s" %(HOMEDIR, v["path"].split("/")[1])
+ with open(path, "w") as config_change:
+ index = modifications["index"]
+ rpl = new_config[index].split(" ")
+ ip_insert = "%s %s %s" %(rpl[0], v["ip"], rpl[2])
+ new_config[index] = ip_insert
+ config_change.writelines(new_config)
+
+ self.qomui_service.copy_rootdir("CHANGE_%s" %provider, "%s/temp" %(HOMEDIR))
+
+ except FileNotFoundError:
+ pass
def search_listitem(self, key):
for row in range(self.serverListWidget.count()):
@@ -2298,8 +2309,9 @@ def retranslateUi(self, ConnectionWidget):
self.uploadLabel.setText(_translate("ConnectionWidget", "Upload:", None))
self.timeLabel.setText(_translate("ConnectionWidget", "Time:", None))
- def setText(self, server_dict, hop_dict, tun):
+ def setText(self, server_dict, hop_dict, tun, tun_hop=None):
self.tun = tun
+ self.tun_hop = tun_hop
self.statusLabel.setText("Active Connection")
city = self.city_port_label(server_dict)
self.ServerWidget.setText(server_dict["name"], server_dict["provider"],
@@ -2318,7 +2330,7 @@ def setText(self, server_dict, hop_dict, tun):
self.hopActiveLabel.setVisible(False)
self.ServerWidget.hide_button(0)
- self.calcThread = NetMon(self.tun)
+ self.calcThread = NetMon(self.tun, self.tun_hop)
self.calcThread.stat.connect(self.show_stats)
self.calcThread.ip.connect(self.show_ip)
self.calcThread.time.connect(self.update_time)
@@ -2370,8 +2382,8 @@ def __init__ (self, parent=None):
def setupUi(self, LineWidget):
self.setAutoFillBackground(True)
- self.setFixedHeight(2)
- self.setBackgroundRole(self.palette().Highlight)
+ self.setFixedHeight(1)
+ #self.setBackgroundRole(self.palette().Highlight)
class NetMon(QtCore.QThread):
stat = QtCore.pyqtSignal(list)
@@ -2379,9 +2391,10 @@ class NetMon(QtCore.QThread):
time = QtCore.pyqtSignal(str)
lost = QtCore.pyqtSignal()
- def __init__(self, tun):
+ def __init__(self, tun, tun_hop=None):
QtCore.QThread.__init__(self)
self.tun = tun
+ self.tun_hop = tun_hop
def run(self):
connected = True
@@ -2412,6 +2425,8 @@ def run(self):
try:
counter = psutil.net_io_counters(pernic=True)[self.tun]
+ if self.tun_hop is not None:
+ tun_hop_test = psutil.net_io_counters(pernic=True)[self.tun_hop]
t1 = time.time()
stat = (counter.bytes_recv, counter.bytes_sent)
DLrate, ULrate = [(now - last) / (t1 - t0) / 1024.0 for now, last in zip(stat, last_stat)]
diff --git a/qomui/qomui_service.py b/qomui/qomui_service.py
index 83bb8e7..b7848fc 100755
--- a/qomui/qomui_service.py
+++ b/qomui/qomui_service.py
@@ -40,6 +40,7 @@ class QomuiDbus(dbus.service.Object):
firewall_opt = 1
hop_dict = {"none" : "none"}
tun = "tun0"
+ tun_hop = "tun0"
connect_status = 0
config = {}
wg_connect = 0
@@ -121,9 +122,12 @@ def disable_ipv6(self, i):
disable_ipv6 = Popen(['sysctl', '-w', 'net.ipv6.conf.all.disable_ipv6=0'])
self.logger.info('(Re-)enabled ipv6')
- @dbus.service.method(BUS_NAME, in_signature='', out_signature='s')
- def return_tun_device(self):
- return self.tun
+ @dbus.service.method(BUS_NAME, in_signature='s', out_signature='s')
+ def return_tun_device(self, tun):
+ if tun == "tun":
+ return self.tun
+ elif tun == "hop":
+ return self.tun_hop
@dbus.service.method(BUS_NAME, in_signature='', out_signature='')
def disconnect(self):
@@ -719,7 +723,12 @@ def ovpn(self, ovpn_file, h, cwd_ovpn):
if self.dns_found == 0:
self.update_dns()
elif line.find('TUN/TAP device') != -1:
- self.tun = line_format.split(" ")[3]
+ if h == "2":
+ self.tun = line_format.split(" ")[3]
+ elif h == "1":
+ self.tun_hop = line_format.split(" ")[3]
+ else:
+ self.tun = line_format.split(" ")[3]
elif line.find('PUSH: Received control message:') != -1:
dns_option_1 = line_format.find('dhcp-option')
if dns_option_1 != -1:
@@ -736,9 +745,26 @@ def ovpn(self, ovpn_file, h, cwd_ovpn):
elif line.find("Restart pause, 10 second(s)") != -1:
self.reply("fail1")
self.logger.info("Connection attempt failed")
+ elif line.find("SIGTERM[soft,auth-failure]") != -1:
+ self.reply("fail1")
+ self.logger.info("Connection attempt failed")
elif line.find('SIGTERM[soft,auth-failure]') != -1:
self.reply("fail2")
self.logger.info("Authentication error while trying to connect")
+ elif line.find('write UDP: Operation not permitted') != -1:
+ ips = []
+ try:
+ hop_ip = self.hop_dict["ip"]
+ ips.append(hop_ip)
+ except:
+ pass
+
+ remote_ip = self.ovpn_dict["ip"]
+ ips.append(remote_ip)
+
+ for ip in ips:
+ rule = (['-I', 'OUTPUT', '1', '-d', '%s' %ip, '-j', 'ACCEPT'])
+ self.allow_ip(ip, rule)
elif line == '':
break
line = ovpn_exe.stdout.readline()
diff --git a/qomui/update.py b/qomui/update.py
index a6802df..601f482 100644
--- a/qomui/update.py
+++ b/qomui/update.py
@@ -564,49 +564,66 @@ def __init__(self, username, password):
self.proton_server_dict = {}
def run(self):
+
+ headers = {'x-pm-appversion': 'Other',
+ 'x-pm-apiversion': '3',
+ 'Accept': 'application/vnd.protonmail.v1+json'
+ }
+
try:
- path = "%s/temp" %DIRECTORY
- server_url = "https://account.protonvpn.com/api/vpn/servers"
- get_servers = json.loads(requests.get(server_url).content.decode("utf-8"))
-
- for s in get_servers["Servers"]:
- name = s["Domain"]
- country = country_translate(s["Country"])
- ip = s["IPs"][0]["EntryIP"]
- self.proton_server_dict[name] = {"name" : name, "country" : country, "city": "", "ip" : ip, "provider" : "ProtonVPN", "tunnel": "OpenVPN"}
+ with requests.Session() as self.session:
+ self.session.headers.update(headers)
+ path = "%s/temp" %DIRECTORY
+ api_url = "https://api.protonmail.ch/vpn/logicals"
+ get_servers = json.loads(self.session.get(api_url).content.decode("utf-8"))
+
+ with open ("%s/s.json" %path, "w") as s:
+ json.dump(get_servers, s)
- cert_url = "https://account.protonvpn.com/api/vpn/config?ID=34&Platform=Linux"
- ovpn = requests.get(cert_url).content.decode("utf-8")
+ for s in get_servers["LogicalServers"]:
+ name = s["Domain"]
+ cc = s["ExitCountry"]
+ if cc == "UK":
+ cc = "GB"
+ country = country_translate(cc)
+ city = s["City"]
+ if city is None:
+ city = ""
+
+ ip = s["Servers"][0]["EntryIP"]
+ self.proton_server_dict[name] = {"name" : name, "country" : country, "city": city, "ip" : ip, "provider" : "ProtonVPN", "tunnel": "OpenVPN"}
- ca_cert = BeautifulSoup(ovpn, "lxml").find("ca")
- with open("%s/proton_ca.crt" %path, "w") as ca:
- ca.write(str(ca_cert))
-
- ta_key = "\n%s" %ovpn.split("")[1]
- with open("%s/proton_ta.key" %path, "w") as ta:
- ta.write(str(ta_key))
+
+ cert_url = "https://account.protonvpn.com/api/vpn/config?ID=34&Platform=Linux"
+ ovpn = requests.get(cert_url).content.decode("utf-8")
+
+ ca_cert = BeautifulSoup(ovpn, "lxml").find("ca")
+ with open("%s/proton_ca.crt" %path, "w") as ca:
+ ca.write(str(ca_cert))
- with open("%s/proton_userpass.txt" %path, "w") as up:
- up.write('%s\n%s' % (self.username, self.password))
+ ta_key = "\n%s" %ovpn.split("")[1]
+ with open("%s/proton_ta.key" %path, "w") as ta:
+ ta.write(str(ta_key))
+
+ with open("%s/proton_userpass.txt" %path, "w") as up:
+ up.write('%s\n%s' % (self.username, self.password))
+
+ self.proton_protocol_dict = {"protocol_1" : {"protocol": "UDP", "port": "1194"},
+ "protocol_2" : {"protocol": "TCP", "port": "443"}
+ }
- self.proton_protocol_dict = {"protocol_1" : {"protocol": "UDP", "port": "1194"},
- "protocol_2" : {"protocol": "TCP", "port": "443"}
- }
+ proton_dict = {"server" : self.proton_server_dict,
+ "protocol" : self.proton_protocol_dict,
+ "provider" : "ProtonVPN",
+ "path" : path
+ }
+
+ self.down_finished.emit(proton_dict)
- proton_dict = {"server" : self.proton_server_dict,
- "protocol" : self.proton_protocol_dict,
- "provider" : "ProtonVPN",
- "path" : path
- }
-
- self.down_finished.emit(proton_dict)
-
except requests.exceptions.RequestException as e:
self.importFail.emit("Network error: no internet connection")
-
-
class AddFolder(QtCore.QThread):
down_finished = QtCore.pyqtSignal(dict)
importFail = QtCore.pyqtSignal(str)
diff --git a/resources/Mullvad_config b/resources/Mullvad_config
index ecfaf4b..8d7c40c 100644
--- a/resources/Mullvad_config
+++ b/resources/Mullvad_config
@@ -9,7 +9,8 @@ persist-tun
remote-cert-tls server
cipher AES-256-CBC
comp-lzo
-tun-ipv6
+fast-io
+ping-restart 60
sndbuf 524288
rcvbuf 524288
verb 3
diff --git a/resources/Windscribe_config b/resources/Windscribe_config
index 86dfe1e..3d27483 100644
--- a/resources/Windscribe_config
+++ b/resources/Windscribe_config
@@ -5,7 +5,9 @@ remote
nobind
resolv-retry infinite
auth SHA512
-cipher AES-256-CBC
+ auth-nocache
+cipher AES-256-GCM
+ncp-ciphers AES-256-GCM:AES-256-CBC:AES-128-GCM
comp-lzo
verb 3
mute-replay-warnings
diff --git a/setup.py b/setup.py
index 473b951..bf7e015 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@
import glob
import os
-VERSION = "0.6.1"
+VERSION = "0.6.2"
data_files = [
('/usr/share/applications/', ['resources/qomui.desktop']),