Skip to content

Commit

Permalink
version 0.6.3
Browse files Browse the repository at this point in the history
  • Loading branch information
corrad1nho committed Jul 30, 2018
1 parent 8faa575 commit 2e75631
Show file tree
Hide file tree
Showing 7 changed files with 187 additions and 59 deletions.
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
##Changelog

version 0.6.3:
- [change] bypass mode supports ipv6 now
- [change] alternative DNS servers are used for bypass
- [change] WireGuard is now written correctly (pull request from zx2c4) - requires all WireGuard configs to be readded
- [change] exit dialog has a 5 sec timeout now
- [change] umask set before chmod to avoid race conditions (pull request from zx2c4)
- [bugfix] bypass should now work properly with WireGuard connections

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)
Expand Down
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,11 @@ Screenshots were taken on Arch Linux/Plasma Arc Dark Theme - Qomui will adapt to

#### Ubuntu

Download and install [DEB-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.2/qomui-0.6.2-amd64.deb)
Download and install [DEB-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.3/qomui-0.6.3-amd64.deb)

#### Fedora

Download and install [RPM-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.2/qomui-0.6.2-1.x86_64.rpm)
Download and install [RPM-Package](https://github.com/corrad1nho/qomui/releases/download/v0.6.3/qomui-0.6.3-1.x86_64.rpm)

#### Arch

Expand Down Expand Up @@ -111,6 +111,13 @@ 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.3:
- [change] bypass mode supports ipv6 now
- [change] WireGuard is now written correctly (pull request from zx2c4) - requires all WireGuard configs to be readded
- [change] exit dialog has a 5 sec timeout now
- [change] umask set before chmod to avoid race conditions (pull request from zx2c4)
- [bugfix] bypass should now work properly with WireGuard connections

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)
Expand All @@ -120,14 +127,3 @@ version 0.6.2:
- [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
- [change] missing flags for Windscribe added
- [change] autocompletion for "c" and "v" options in cli
- [change] most cli commands are not case-sensitive anymore
- [bugfix] alternative dns servers not parsed correctly
- [bugfix] crashes when loading default configuration
- [bugfix] configs are not imported if url cannot be resolved
- [bugfix] old connection not killed after network change detected (in rare cases)

2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.6.2
0.6.3
71 changes: 57 additions & 14 deletions qomui/bypass.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
default_interface = None
ROOTDIR = "/usr/share/qomui/"

def create_cgroup(user, group, default_interface, default_gateway):
def create_cgroup(user, group, default_interface, default_gateway, default_interface_6, default_gateway_6):

cleanup = delete_cgroup(default_interface)
cleanup = delete_cgroup(default_interface, default_interface_6)

cgroup_iptables = [["-t", "mangle", "-A", "OUTPUT", "-m", "cgroup",
"--cgroup", "0x00110011", "-j", "MARK", "--set-mark", "11"],
Expand Down Expand Up @@ -46,30 +46,66 @@ def create_cgroup(user, group, default_interface, default_gateway):
for rule in cgroup_iptables:
firewall.add_rule(rule)

if default_gateway_6 != "None":
cgroup_iptables.pop(1)
cgroup_iptables.insert(1, ["-t", "nat", "-A", "POSTROUTING", "-m", "cgroup",
"--cgroup", "0x00110011", "-o", "%s" %default_interface_6 , "-j", "MASQUERADE"])

for rule in cgroup_iptables:
firewall.add_rule_6(rule)

else:
logging.debug("Blocking ipv6 via bypass_qomui")
cgroup_iptables.pop(1)
cgroup_iptables.insert(1, ["-t", "nat", "-A", "POSTROUTING", "-m", "cgroup",
"--cgroup", "0x00110011", "-o", "%s" %default_interface , "-j", "MASQUERADE"])
cgroup_iptables.pop(2)
cgroup_iptables.insert(2, ["-I", "OUTPUT", "1", "-m", "cgroup", "--cgroup", "0x00110011", "-j", "DROP"])
cgroup_iptables.pop(3)
cgroup_iptables.insert(3, ["-I", "OUTPUT", "1", "-m", "cgroup", "--cgroup", "0x00110011", "-j", "DROP"])

for rule in cgroup_iptables:
firewall.add_rule_6(rule)

route_cmds = [
["ip", "route", "flush", "table", "bypass_qomui"],
["ip", "rule", "add", "fwmark", "11", "table", "bypass_qomui"]
]

try:
check_call(["ip", "route", "flush", "table", "bypass_qomui"])
except CalledProcessError:
pass

try:
check_call(["ip", "route", "flush", "table", "bypass_qomui"])
check_call(["ip", "rule", "add", "fwmark", "11", "table", "bypass_qomui"])
check_call(["ip", "route", "add", "default", "via", "%s" %default_gateway, "table", "bypass_qomui"])
check_call(["ip", "route", "add", "default", "via",
"%s" %default_gateway, "dev", "%s" %default_interface, "table", "bypass_qomui"])
except CalledProcessError:
logging.error("Could not set ipv4 routes for cgroup")

try:
check_call(["ip", "-6", "route", "flush", "table", "bypass_qomui"])
check_call(["ip", "-6", "rule", "add", "fwmark", "11", "table", "bypass_qomui"])
check_call(["ip", "-6", "route", "add", "default", "via",
"%s" %default_gateway_6, "dev", "%s" %default_interface_6, "table", "bypass_qomui"])
except CalledProcessError:
logging.error("Could not set ipv6 routes for cgroup")

try:
check_call(["cgcreate", "-t", "%s:%s" %(user, group), "-a" "%s:%s" %(user, group), "-g", "net_cls:bypass_qomui"])
except CalledProcessError:
self.logger.error("Configuration of cgroup failed")
logging.error("Creating cgroup failed")

with open ("/proc/sys/net/ipv4/conf/all/rp_filter", 'w') as rp_edit_all:
rp_edit_all.write("2")
with open ("/proc/sys/net/ipv4/conf/%s/rp_filter" %default_interface, 'w') as rp_edit_int:
rp_edit_int.write("2")

logging.info("Succesfully create cgroup to bypass OpenVPN tunnel")
logging.info("Succesfully created cgroup to bypass OpenVPN tunnel")

try:
dnsmasq = Popen(["dnsmasq", "--port=5354", "--interface=%s" %default_interface])

return dnsmasq.pid + 2
except CalledProcessError:
logging.error("Failed to start dnsmasq for cgroup qomui_bypass")
return None

def delete_cgroup(default_interface):
def delete_cgroup(default_interface, default_interface_6):

cgroup_iptables_del = [["-t", "mangle", "-D", "OUTPUT", "-m", "cgroup",
"--cgroup", "0x00110011", "-j", "MARK", "--set-mark", "11"],
Expand Down Expand Up @@ -99,6 +135,13 @@ def delete_cgroup(default_interface):
for rule in cgroup_iptables_del:
firewall.add_rule(rule)

cgroup_iptables_del.pop(1)
cgroup_iptables_del.insert(1, ["-t", "nat", "-D", "POSTROUTING", "-m", "cgroup",
"--cgroup", "0x00110011", "-o", "%s" %default_interface_6 , "-j", "MASQUERADE"])

for rule in cgroup_iptables_del:
firewall.add_rule_6(rule)

try:
os.rmdir(cgroup_path)
except (OSError, FileNotFoundError):
Expand Down
50 changes: 37 additions & 13 deletions qomui/qomui_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ def leaveEvent(self, event):
def sizeHint(self):
return QtCore.QSize(25, 25)


class QomuiGui(QtWidgets.QWidget):
status = "inactive"
server_dict = {}
Expand Down Expand Up @@ -965,20 +964,45 @@ def restoreUi(self, reason):
self.showNormal()

def closeEvent(self, event):
ret = self.messageBox("Do you want to exit program or minimize to tray?",
"",
buttons=["Minimize", "Exit", "Cancel"],
icon="Question"
)
if ret == 1:
self.tray.hide()
self.kill()
event.accept()
self.exit_event = event
self.confirm = QtWidgets.QMessageBox()
self.timeout = 5
self.confirm.setText("Do you want to exit program or minimize to tray?")
info = "Closing in %s seconds" %self.timeout
self.confirm.setInformativeText(info)
self.confirm.setIcon(QtWidgets.QMessageBox.Question)
self.confirm.addButton(QtWidgets.QPushButton("Minimize"), QtWidgets.QMessageBox.NoRole)
self.confirm.addButton(QtWidgets.QPushButton("Exit"), QtWidgets.QMessageBox.YesRole)
self.confirm.addButton(QtWidgets.QPushButton("Cancel"), QtWidgets.QMessageBox.RejectRole)
self.timer = QtCore.QTimer(self)
self.timer.setInterval(1000)
self.timer.timeout.connect(self.change_timeout)
self.timer.start()

ret = self.confirm.exec_()
self.timer.stop()

if ret == 2:
self.exit_event.ignore()

elif ret == 0:
event.ignore()
self.hide()
elif ret == 2:
event.ignore()

elif ret == 1:
self.tray.hide()
self.kill()
self.exit_event.accept()

def change_timeout(self):
self.timeout -= 1
info = "Closing in %s seconds" %self.timeout
self.confirm.setInformativeText(info)
if self.timeout <= 0:
self.timer.stop()
self.confirm.hide()
self.tray.hide()
self.kill()
self.exit_event.accept()

def load_json(self, json_file):
try:
Expand Down
91 changes: 74 additions & 17 deletions qomui/qomui_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import logging.handlers
import json
import signal
from subprocess import Popen, PIPE, check_output, CalledProcessError, STDOUT
from subprocess import Popen, PIPE, check_output, check_call, CalledProcessError, STDOUT
import dbus
import dbus.service
from dbus.mainloop.pyqt5 import DBusQtMainLoop
Expand Down Expand Up @@ -348,36 +348,76 @@ def bypass(self, ug):
except AttributeError:
pass

default_gateway = self.default_gateway_check()["gateway"]
if default_gateway != "None":
default_routes = self.default_gateway_check()
default_gateway_4 = default_routes["gateway"]
default_gateway_6 = default_routes["gateway_6"]
self.default_interface_4 = default_routes["interface"]
self.default_interface_6 = default_routes["interface_6"]

if default_gateway_4 != "None" or default_gateway_6 != "None":
try:
if self.config["bypass"] == 1:
pid = bypass.create_cgroup(self.ug["user"], self.ug["group"],
self.default_interface, default_gateway
bypass.create_cgroup(self.ug["user"], self.ug["group"],
self.default_interface_4, default_gateway_4,
self.default_interface_6, default_gateway_6
)
self.dnsmasq_pid = (pid, "dnsmasq")
self.logger.debug("dnsmasq-PID = %s" %pid)

if self.default_interface_4 != "None":
interface = self.default_interface_4

else:
interface = self.default_interface_6

try:
dnsmasq = Popen(["dnsmasq", "--port=5354", "--interface=%s" %interface,
"--server=%s" %self.config["alt_dns1"],
"--server=%s" %self.config["alt_dns2"]
])

self.logger.debug(dnsmasq.pid +2)
self.dnsmasq_pid = (dnsmasq.pid +2, "dnsmasq")

except CalledProcessError:
logging.error("Failed to start dnsmasq for cgroup qomui_bypass")

elif self.config["bypass"] == 0:

try:
bypass.delete_cgroup(self.default_interface)
bypass.delete_cgroup(self.default_interface_4, self.default_interface_6)
except AttributeError:
pass

except KeyError:
self.logger.warning('Could not read all values from file')
self.logger.warning('Config file corrupted - bypass option does not exist')

@dbus.service.method(BUS_NAME, in_signature='', out_signature='a{ss}')
def default_gateway_check(self):
try:
route_cmd = ["ip", "route", "show", "default", "0.0.0.0/0"]
default_route = check_output(route_cmd).decode("utf-8")
parse_route = default_route.split(" ")
self.default_interface = parse_route[4]
default_gateway = parse_route[2]
default_interface = parse_route[4]
return {"gateway" : default_gateway, "interface" : default_interface}
default_gateway_4 = parse_route[2]
default_interface_4 = parse_route[4]

except (CalledProcessError, IndexError):
self.logger.info('Could not identify default gateway - no network connectivity')
return {"gateway" : "None", "interface" : "None"}
default_gateway_4 = "None"
default_interface_4 = "None"

try:
route_cmd = ["ip", "-6", "route", "show", "default", "::/0"]
default_route = check_output(route_cmd).decode("utf-8")
parse_route = default_route.split(" ")
default_gateway_6 = parse_route[2]
default_interface_6 = parse_route[4]

except (CalledProcessError, IndexError):
self.logger.info('Could not identify default gateway for ipv6 - no network connectivity')
default_gateway_6 = "None"
default_interface_6 = "None"

return {"gateway" : default_gateway_4, "gateway_6" : default_gateway_6,
"interface" : default_interface_4, "interface_6" : default_interface_6}

@dbus.service.signal(BUS_NAME, signature='s')
def reply(self, msg):
Expand Down Expand Up @@ -475,8 +515,8 @@ def wireguard(self):

else:
shutil.copyfile("%s/%s" %(ROOTDIR, self.ovpn_dict["path"]), path)
os.umask(oldmask)

os.umask(oldmask)
Popen(['chmod', '0600', path])

self.wg(path)
Expand Down Expand Up @@ -655,8 +695,8 @@ def wg(self, wg_file):
name = self.ovpn_dict["name"]
self.logger.info("Establishing connection to %s" %name)

wg_rules = [["-I", "INPUT", "1", "-i", "wg_qomui", "-j", "ACCEPT"],
["-I", "OUTPUT", "1", "-o", "wg_qomui", "-j", "ACCEPT"]
wg_rules = [["-I", "INPUT", "2", "-i", "wg_qomui", "-j", "ACCEPT"],
["-I", "OUTPUT", "2", "-o", "wg_qomui", "-j", "ACCEPT"]
]

for rule in wg_rules:
Expand All @@ -671,6 +711,23 @@ def wg(self, wg_file):
for line in cmd_wg.stdout:
logging.info(line)
self.wg_connect = 1

#Necessary, otherwise bypass mode breaks
if self.config["bypass"] == 1:

try:
check_call(["ip", "rule", "del", "fwmark", "11", "table", "bypass_qomui"])
check_call(["ip", "-6", "rule", "del", "fwmark", "11", "table", "bypass_qomui"])
except CalledProcessError:
pass

try:
check_call(["ip", "rule", "add", "fwmark", "11", "table", "bypass_qomui"])
check_call(["ip", "-6", "rule", "add", "fwmark", "11", "table", "bypass_qomui"])
self.logger.debug("Packet classification for bypass table reset")
except CalledProcessError:
self.logger.warning("Could not reset packet classification for bypass table")

self.reply("success")

except (CalledProcessError, FileNotFoundError):
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import glob
import os

VERSION = "0.6.2"
VERSION = "0.6.3"

data_files = [
('/usr/share/applications/', ['resources/qomui.desktop']),
Expand Down

0 comments on commit 2e75631

Please sign in to comment.