mirror of
https://github.com/evilsocket/opensnitch.git
synced 2025-03-04 08:34:40 +01:00
ui: allow to stop/start interception from systray
- removed unused import time. - allow to pause/start interception from tray contextual menu. - improved case when the daemon is in paused state, and the GUI is launched. There's more work yet to do to improve the states when there're several nodes connected. closes #398
This commit is contained in:
parent
23b61f5341
commit
966342cbbc
3 changed files with 140 additions and 40 deletions
|
@ -225,7 +225,7 @@ func acceptOrDeny(packet *netfilter.Packet, con *conman.Connection) *rule.Rule {
|
|||
// 1) connected and running and 2) we are not already asking
|
||||
if uiClient.Connected() == false || uiClient.GetIsAsking() == true {
|
||||
applyDefaultAction(packet)
|
||||
log.Debug("UI is not running or busy")
|
||||
log.Debug("UI is not running or busy, connected: %v, running: %v", uiClient.Connected(), uiClient.GetIsAsking())
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -379,6 +379,9 @@ func main() {
|
|||
}
|
||||
repeatPktChan = repeatQueue.Packets()
|
||||
|
||||
// queue is ready, run firewall rules
|
||||
firewall.Init(&queueNum)
|
||||
|
||||
uiClient = ui.NewClient(uiSocket, stats, rules)
|
||||
if overwriteLogging() {
|
||||
setupLogging()
|
||||
|
@ -390,9 +393,6 @@ func main() {
|
|||
}
|
||||
monitor.Init()
|
||||
|
||||
// queue is ready, run firewall rules
|
||||
firewall.Init(&queueNum)
|
||||
|
||||
log.Info("Running on netfilter queue #%d ...", queueNum)
|
||||
for {
|
||||
select {
|
||||
|
|
|
@ -919,14 +919,13 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
self.startButton.setIcon(self.iconStart)
|
||||
return
|
||||
|
||||
self.update_interception_status(self.startButton.isChecked())
|
||||
self._status_changed_trigger.emit(self.startButton.isChecked())
|
||||
|
||||
if self.startButton.isChecked():
|
||||
self._update_status_label(running=True, text=self.FIREWALL_RUNNING)
|
||||
nid, noti = self._nodes.start_interception(_callback=self._notification_callback)
|
||||
self._status_changed_trigger.emit(False)
|
||||
else:
|
||||
self._update_status_label(running=False, text=self.FIREWALL_DISABLED)
|
||||
nid, noti = self._nodes.stop_interception(_callback=self._notification_callback)
|
||||
self._status_changed_trigger.emit(True)
|
||||
|
||||
self._notifications_sent[nid] = noti
|
||||
|
||||
|
@ -1382,6 +1381,14 @@ class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
|
|||
|
||||
return tableWidget
|
||||
|
||||
def update_interception_status(self, enabled):
|
||||
self.startButton.setDown(enabled)
|
||||
self.startButton.setChecked(enabled)
|
||||
if enabled:
|
||||
self._update_status_label(running=True, text=self.FIREWALL_RUNNING)
|
||||
else:
|
||||
self._update_status_label(running=False, text=self.FIREWALL_DISABLED)
|
||||
|
||||
# launched from a thread
|
||||
def update(self, is_local=True, stats=None, need_query_update=True):
|
||||
# lock mandatory when there're multiple clients
|
||||
|
|
|
@ -3,7 +3,6 @@ from PyQt5 import QtWidgets, QtGui, QtCore
|
|||
from datetime import datetime, timedelta
|
||||
from threading import Thread, Lock, Event
|
||||
import grpc
|
||||
import time
|
||||
import os
|
||||
import socket
|
||||
import fcntl
|
||||
|
@ -30,7 +29,13 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
_new_remote_trigger = QtCore.pyqtSignal(str, ui_pb2.PingRequest)
|
||||
_update_stats_trigger = QtCore.pyqtSignal(str, str, ui_pb2.PingRequest)
|
||||
_version_warning_trigger = QtCore.pyqtSignal(str, str)
|
||||
_status_change_trigger = QtCore.pyqtSignal()
|
||||
_notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
|
||||
|
||||
MENU_ENTRY_STATS = QtCore.QCoreApplication.translate("contextual_menu", "Statistics")
|
||||
MENU_ENTRY_FW_ENABLE = QtCore.QCoreApplication.translate("contextual_menu", "Enable")
|
||||
MENU_ENTRY_FW_DISABLE = QtCore.QCoreApplication.translate("contextual_menu", "Disable")
|
||||
MENU_ENTRY_HELP = QtCore.QCoreApplication.translate("contextual_menu", "Help")
|
||||
MENU_ENTRY_CLOSE = QtCore.QCoreApplication.translate("contextual_menu", "Close")
|
||||
|
||||
def __init__(self, app, on_exit):
|
||||
super(UIService, self).__init__()
|
||||
|
@ -46,6 +51,7 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
self._version_warning_shown = False
|
||||
self._asking = False
|
||||
self._connected = False
|
||||
self._fw_enabled = False
|
||||
self._path = os.path.abspath(os.path.dirname(__file__))
|
||||
self._app = app
|
||||
self._on_exit = on_exit
|
||||
|
@ -94,7 +100,6 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
# https://stackoverflow.com/questions/40288921/pyqt-after-messagebox-application-quits-why
|
||||
self._app.setQuitOnLastWindowClosed(False)
|
||||
self._version_warning_trigger.connect(self._on_diff_versions)
|
||||
self._status_change_trigger.connect(self._on_status_change)
|
||||
self._new_remote_trigger.connect(self._on_new_remote)
|
||||
self._update_stats_trigger.connect(self._on_update_stats)
|
||||
self._stats_dialog._shown_trigger.connect(self._on_stats_dialog_shown)
|
||||
|
@ -122,23 +127,24 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
|
||||
def _setup_tray(self):
|
||||
self._menu = QtWidgets.QMenu()
|
||||
self._stats_action = self._menu.addAction(QtCore.QCoreApplication.translate("contextual_menu","Statistics"))
|
||||
|
||||
self._tray = QtWidgets.QSystemTrayIcon(self.off_icon)
|
||||
self._tray.setContextMenu(self._menu)
|
||||
self._tray.activated.connect(self._on_tray_icon_activated)
|
||||
|
||||
self._menu.addAction(QtCore.QCoreApplication.translate("contextual_menu", "Help")).triggered.connect(
|
||||
self._menu.addAction(self.MENU_ENTRY_STATS).triggered.connect(self._show_stats_dialog)
|
||||
self._menu_enable_fw = self._menu.addAction(self.MENU_ENTRY_FW_DISABLE)
|
||||
self._menu_enable_fw.setEnabled(False)
|
||||
self._menu_enable_fw.triggered.connect(self._on_enable_interception_clicked)
|
||||
self._menu.addAction(self.MENU_ENTRY_HELP).triggered.connect(
|
||||
lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_URL))
|
||||
)
|
||||
|
||||
self._stats_action.triggered.connect(self._show_stats_dialog)
|
||||
self._menu.addAction(QtCore.QCoreApplication.translate("contextual_menu", "Close")).triggered.connect(self._on_close)
|
||||
self._menu.addAction(self.MENU_ENTRY_CLOSE).triggered.connect(self._on_close)
|
||||
|
||||
self._tray.show()
|
||||
if not self._tray.isSystemTrayAvailable():
|
||||
self._stats_dialog.show()
|
||||
|
||||
|
||||
def _on_tray_icon_activated(self, reason):
|
||||
if reason == QtWidgets.QSystemTrayIcon.Trigger or reason == QtWidgets.QSystemTrayIcon.MiddleClick:
|
||||
if self._stats_dialog.isVisible() and not self._stats_dialog.isMinimized():
|
||||
|
@ -157,25 +163,13 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
self._on_exit()
|
||||
|
||||
def _show_stats_dialog(self):
|
||||
if self._connected:
|
||||
if self._connected and not self._fw_enabled:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
self._stats_dialog.show()
|
||||
|
||||
@QtCore.pyqtSlot(bool)
|
||||
def _on_stats_status_changed(self, paused):
|
||||
if paused:
|
||||
self._tray.setIcon(self.pause_icon)
|
||||
else:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def _on_status_change(self):
|
||||
self._stats_dialog.daemon_connected = self._connected
|
||||
self._stats_dialog.update_status()
|
||||
if self._connected:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
else:
|
||||
self._tray.setIcon(self.off_icon)
|
||||
def _on_stats_status_changed(self, enabled):
|
||||
self._update_fw_status(enabled)
|
||||
|
||||
@QtCore.pyqtSlot(str, str)
|
||||
def _on_diff_versions(self, daemon_ver, ui_ver):
|
||||
|
@ -207,13 +201,95 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
@QtCore.pyqtSlot()
|
||||
def _on_stats_dialog_shown(self):
|
||||
if self._connected:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
if self._fw_enabled:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
else:
|
||||
self._tray.setIcon(self.pause_icon)
|
||||
else:
|
||||
self._tray.setIcon(self.off_icon)
|
||||
|
||||
@QtCore.pyqtSlot(ui_pb2.NotificationReply)
|
||||
def _on_notification_reply(self, reply):
|
||||
if reply.code == ui_pb2.ERROR:
|
||||
self._tray.showMessage("Error",
|
||||
reply.data,
|
||||
QtWidgets.QSystemTrayIcon.Information,
|
||||
5000)
|
||||
|
||||
def _on_remote_stats_menu(self, address):
|
||||
self._remote_stats[address]['dialog'].show()
|
||||
|
||||
def _on_enable_interception_clicked(self):
|
||||
self._enable_interception(self._fw_enabled)
|
||||
|
||||
def _update_fw_status(self, enabled):
|
||||
"""_update_fw_status updates the status of the menu entry
|
||||
to disable or enable the firewall of the daemon.
|
||||
"""
|
||||
if self._connected == False:
|
||||
return
|
||||
|
||||
self._stats_dialog.update_interception_status(enabled)
|
||||
if enabled:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
self._menu_enable_fw.setText(self.MENU_ENTRY_FW_DISABLE)
|
||||
else:
|
||||
self._tray.setIcon(self.pause_icon)
|
||||
self._menu_enable_fw.setText(self.MENU_ENTRY_FW_ENABLE)
|
||||
self._fw_enabled = enabled
|
||||
|
||||
def _set_daemon_connected(self, connected):
|
||||
"""_set_daemon_connected only updates the connection status of the daemon(s),
|
||||
regardless if the fw is enabled or not.
|
||||
There're 3 states:
|
||||
- daemon connected
|
||||
- daemon not connected
|
||||
- daemon connected and firewall enabled/disabled
|
||||
"""
|
||||
self._stats_dialog.daemon_connected = connected
|
||||
self._connected = connected
|
||||
|
||||
# if there're more than 1 node, override connection status
|
||||
if self._nodes.count() >= 1:
|
||||
self._connected = True
|
||||
self._stats_dialog.daemon_connected = True
|
||||
|
||||
if self._nodes.count() == 1:
|
||||
self._menu_enable_fw.setEnabled(True)
|
||||
|
||||
if self._nodes.count() == 0 or self._nodes.count() > 1:
|
||||
self._menu_enable_fw.setEnabled(False)
|
||||
|
||||
self._stats_dialog.update_status()
|
||||
|
||||
if self._connected:
|
||||
self._tray.setIcon(self.white_icon)
|
||||
else:
|
||||
self._fw_enabled = False
|
||||
self._tray.setIcon(self.off_icon)
|
||||
|
||||
def _enable_interception(self, enable):
|
||||
if self._connected == False:
|
||||
return
|
||||
if self._nodes.count() == 0:
|
||||
self._tray.showMessage("No nodes connected",
|
||||
"",
|
||||
QtWidgets.QSystemTrayIcon.Information,
|
||||
5000)
|
||||
return
|
||||
if self._nodes.count() > 1:
|
||||
print("enable interception for all nodes not supported yet")
|
||||
return
|
||||
|
||||
if enable:
|
||||
nid, noti = self._nodes.stop_interception(_callback=self._notification_callback)
|
||||
else:
|
||||
nid, noti = self._nodes.start_interception(_callback=self._notification_callback)
|
||||
|
||||
self._fw_enabled = not enable
|
||||
|
||||
self._stats_dialog._status_changed_trigger.emit(not enable)
|
||||
|
||||
def _check_versions(self, daemon_version):
|
||||
lMayor, lMinor, lPatch = version.split(".")
|
||||
rMayor, rMinor, rPatch = daemon_version.split(".")
|
||||
|
@ -435,6 +511,16 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
QtWidgets.QSystemTrayIcon.Information,
|
||||
5000)
|
||||
n = self._nodes.add(context, node_config)
|
||||
|
||||
if n != None:
|
||||
self._set_daemon_connected(True)
|
||||
# if there're more than one node, we can't update the status
|
||||
# based on the fw status, only if the daemon is running or not
|
||||
if self._nodes.count() <= 1:
|
||||
self._update_fw_status(node_config.isFirewallRunning)
|
||||
else:
|
||||
self._update_fw_status(True)
|
||||
|
||||
except Exception as e:
|
||||
print("[Notifications] exception adding new node:", e)
|
||||
context.cancel()
|
||||
|
@ -457,17 +543,24 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
stop_event.set()
|
||||
self._delete_node(context.peer())
|
||||
|
||||
if self._nodes.count() == 0:
|
||||
self._connected = False
|
||||
self._status_change_trigger.emit()
|
||||
self._set_daemon_connected(False)
|
||||
# TODO: handle the situation when a node disconnects, and the
|
||||
# remaining node has the fw disabled.
|
||||
#if self._nodes.count() == 1:
|
||||
# nd = self._nodes.get_nodes()
|
||||
# if nd[0].get_config().isFirewallRunning:
|
||||
|
||||
if self._is_local_request(proto, addr) == False:
|
||||
self._tray.showMessage("node exited",
|
||||
"({0})".format(context.peer()),
|
||||
QtWidgets.QSystemTrayIcon.Information,
|
||||
5000)
|
||||
|
||||
context.add_callback(_on_client_closed)
|
||||
|
||||
# TODO: move to notifications.py
|
||||
def new_node_message():
|
||||
print("new node connected, listening for client responses...", addr)
|
||||
self._connected = True
|
||||
self._status_change_trigger.emit()
|
||||
|
||||
while self._exit == False:
|
||||
try:
|
||||
|
@ -481,9 +574,9 @@ class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
|
|||
print("[Notifications] Node {0} exited".format(addr))
|
||||
break
|
||||
except grpc.RpcError as e:
|
||||
print("[Notifications] grpc exception new_node_message(): ", addr)
|
||||
print("[Notifications] grpc exception new_node_message(): ", addr, in_message)
|
||||
except Exception as e:
|
||||
print("[Notifications] unexpected exception new_node_message(): ", addr, e)
|
||||
print("[Notifications] unexpected exception new_node_message(): ", addr, e, in_message)
|
||||
|
||||
read_thread = Thread(target=new_node_message)
|
||||
read_thread.daemon = True
|
||||
|
|
Loading…
Add table
Reference in a new issue