Import opensnitch_1.6.9.orig.tar.gz
authorPetter Reinholdtsen <pere@debian.org>
Tue, 29 Apr 2025 05:35:00 +0000 (07:35 +0200)
committerPetter Reinholdtsen <pere@debian.org>
Tue, 29 Apr 2025 05:35:00 +0000 (07:35 +0200)
[dgit import orig opensnitch_1.6.9.orig.tar.gz]

317 files changed:
.github/FUNDING.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/bug_report.md [new file with mode: 0644]
.github/ISSUE_TEMPLATE/config.yml [new file with mode: 0644]
.github/ISSUE_TEMPLATE/feature-request.md [new file with mode: 0644]
.github/workflows/build_ebpf_modules.yml [new file with mode: 0644]
.github/workflows/generic_validations.yml [new file with mode: 0644]
.github/workflows/go.yml [new file with mode: 0644]
.gitignore [new file with mode: 0644]
LICENSE [new file with mode: 0644]
Makefile [new file with mode: 0644]
README.md [new file with mode: 0644]
daemon/.gitignore [new file with mode: 0644]
daemon/Gopkg.toml [new file with mode: 0644]
daemon/Makefile [new file with mode: 0644]
daemon/conman/connection.go [new file with mode: 0644]
daemon/conman/connection_test.go [new file with mode: 0644]
daemon/core/core.go [new file with mode: 0644]
daemon/core/ebpf.go [new file with mode: 0644]
daemon/core/gzip.go [new file with mode: 0644]
daemon/core/system.go [new file with mode: 0644]
daemon/core/version.go [new file with mode: 0644]
daemon/data/rules/000-allow-localhost.json [new file with mode: 0644]
daemon/default-config.json [new file with mode: 0644]
daemon/dns/ebpfhook.go [new file with mode: 0644]
daemon/dns/parse.go [new file with mode: 0644]
daemon/dns/systemd/monitor.go [new file with mode: 0644]
daemon/dns/track.go [new file with mode: 0644]
daemon/firewall/common/common.go [new file with mode: 0644]
daemon/firewall/config/config.go [new file with mode: 0644]
daemon/firewall/iptables/iptables.go [new file with mode: 0644]
daemon/firewall/iptables/monitor.go [new file with mode: 0644]
daemon/firewall/iptables/rules.go [new file with mode: 0644]
daemon/firewall/iptables/system.go [new file with mode: 0644]
daemon/firewall/nftables/chains.go [new file with mode: 0644]
daemon/firewall/nftables/chains_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/counter.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/counter_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/ct.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/ct_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/enums.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/ether.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/ether_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/iface.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/iface_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/ip.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/ip_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/limit.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/log.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/log_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/meta.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/meta_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/nat.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/nat_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/notrack.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/operator.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/port.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/port_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/protocol.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/protocol_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/quota.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/quota_test.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/utils.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/verdict.go [new file with mode: 0644]
daemon/firewall/nftables/exprs/verdict_test.go [new file with mode: 0644]
daemon/firewall/nftables/monitor.go [new file with mode: 0644]
daemon/firewall/nftables/monitor_test.go [new file with mode: 0644]
daemon/firewall/nftables/nftables.go [new file with mode: 0644]
daemon/firewall/nftables/nftest/nftest.go [new file with mode: 0644]
daemon/firewall/nftables/nftest/test_utils.go [new file with mode: 0644]
daemon/firewall/nftables/nftest/utils.go [new file with mode: 0644]
daemon/firewall/nftables/parser.go [new file with mode: 0644]
daemon/firewall/nftables/rule_helpers.go [new file with mode: 0644]
daemon/firewall/nftables/rules.go [new file with mode: 0644]
daemon/firewall/nftables/rules_test.go [new file with mode: 0644]
daemon/firewall/nftables/system.go [new file with mode: 0644]
daemon/firewall/nftables/system_test.go [new file with mode: 0644]
daemon/firewall/nftables/tables.go [new file with mode: 0644]
daemon/firewall/nftables/tables_test.go [new file with mode: 0644]
daemon/firewall/nftables/testdata/test-sysfw-conf.json [new file with mode: 0644]
daemon/firewall/nftables/utils.go [new file with mode: 0644]
daemon/firewall/nftables/utils_test.go [new file with mode: 0644]
daemon/firewall/rules.go [new file with mode: 0644]
daemon/go.mod [new file with mode: 0644]
daemon/go.sum [new file with mode: 0644]
daemon/log/formats/csv.go [new file with mode: 0644]
daemon/log/formats/formats.go [new file with mode: 0644]
daemon/log/formats/json.go [new file with mode: 0644]
daemon/log/formats/rfc3164.go [new file with mode: 0644]
daemon/log/formats/rfc5424.go [new file with mode: 0644]
daemon/log/log.go [new file with mode: 0644]
daemon/log/loggers/logger.go [new file with mode: 0644]
daemon/log/loggers/remote.go [new file with mode: 0644]
daemon/log/loggers/remote_syslog.go [new file with mode: 0644]
daemon/log/loggers/syslog.go [new file with mode: 0644]
daemon/main.go [new file with mode: 0644]
daemon/netfilter/packet.go [new file with mode: 0644]
daemon/netfilter/queue.c [new file with mode: 0644]
daemon/netfilter/queue.go [new file with mode: 0644]
daemon/netfilter/queue.h [new file with mode: 0644]
daemon/netlink/ifaces.go [new file with mode: 0644]
daemon/netlink/socket.go [new file with mode: 0644]
daemon/netlink/socket_linux.go [new file with mode: 0644]
daemon/netlink/socket_test.go [new file with mode: 0644]
daemon/netstat/entry.go [new file with mode: 0644]
daemon/netstat/find.go [new file with mode: 0644]
daemon/netstat/parse.go [new file with mode: 0644]
daemon/opensnitchd-dinit [new file with mode: 0644]
daemon/opensnitchd-openrc [new file with mode: 0755]
daemon/opensnitchd.service [new file with mode: 0644]
daemon/procmon/activepids.go [new file with mode: 0644]
daemon/procmon/activepids_test.go [new file with mode: 0644]
daemon/procmon/audit/client.go [new file with mode: 0644]
daemon/procmon/audit/parse.go [new file with mode: 0644]
daemon/procmon/cache.go [new file with mode: 0644]
daemon/procmon/cache_test.go [new file with mode: 0644]
daemon/procmon/details.go [new file with mode: 0644]
daemon/procmon/ebpf/cache.go [new file with mode: 0644]
daemon/procmon/ebpf/debug.go [new file with mode: 0644]
daemon/procmon/ebpf/ebpf.go [new file with mode: 0644]
daemon/procmon/ebpf/events.go [new file with mode: 0644]
daemon/procmon/ebpf/find.go [new file with mode: 0644]
daemon/procmon/ebpf/monitor.go [new file with mode: 0644]
daemon/procmon/ebpf/utils.go [new file with mode: 0644]
daemon/procmon/find.go [new file with mode: 0644]
daemon/procmon/find_test.go [new file with mode: 0644]
daemon/procmon/monitor/init.go [new file with mode: 0644]
daemon/procmon/parse.go [new file with mode: 0644]
daemon/procmon/process.go [new file with mode: 0644]
daemon/procmon/process_test.go [new file with mode: 0644]
daemon/rule/loader.go [new file with mode: 0644]
daemon/rule/loader_test.go [new file with mode: 0644]
daemon/rule/operator.go [new file with mode: 0644]
daemon/rule/operator_lists.go [new file with mode: 0644]
daemon/rule/operator_test.go [new file with mode: 0644]
daemon/rule/rule.go [new file with mode: 0644]
daemon/rule/rule_test.go [new file with mode: 0644]
daemon/rule/testdata/000-allow-chrome.json [new file with mode: 0644]
daemon/rule/testdata/001-deny-chrome.json [new file with mode: 0644]
daemon/rule/testdata/invalid-regexp-list.json [new file with mode: 0644]
daemon/rule/testdata/invalid-regexp.json [new file with mode: 0644]
daemon/rule/testdata/lists/domains/domainlists.txt [new file with mode: 0644]
daemon/rule/testdata/lists/ips/ips.txt [new file with mode: 0644]
daemon/rule/testdata/lists/nets/nets.txt [new file with mode: 0644]
daemon/rule/testdata/lists/regexp/domainsregexp.txt [new file with mode: 0644]
daemon/rule/testdata/live_reload/test-live-reload-delete.json [new file with mode: 0644]
daemon/rule/testdata/live_reload/test-live-reload-remove.json [new file with mode: 0644]
daemon/rule/testdata/rule-disabled-operator-list-expanded.json [new file with mode: 0644]
daemon/rule/testdata/rule-disabled-operator-list.json [new file with mode: 0644]
daemon/rule/testdata/rule-operator-list-data-empty.json [new file with mode: 0644]
daemon/rule/testdata/rule-operator-list.json [new file with mode: 0644]
daemon/statistics/event.go [new file with mode: 0644]
daemon/statistics/stats.go [new file with mode: 0644]
daemon/system-fw.json [new file with mode: 0644]
daemon/ui/alerts.go [new file with mode: 0644]
daemon/ui/auth/auth.go [new file with mode: 0644]
daemon/ui/client.go [new file with mode: 0644]
daemon/ui/client_test.go [new file with mode: 0644]
daemon/ui/config/config.go [new file with mode: 0644]
daemon/ui/config_utils.go [new file with mode: 0644]
daemon/ui/notifications.go [new file with mode: 0644]
daemon/ui/protocol/.gitkeep [new file with mode: 0644]
daemon/ui/testdata/default-config.json [new file with mode: 0644]
daemon/ui/testdata/orig-default-config.json [new file with mode: 0644]
ebpf_prog/Makefile [new file with mode: 0644]
ebpf_prog/README [new file with mode: 0644]
ebpf_prog/arm-clang-asm-fix.patch [new file with mode: 0644]
ebpf_prog/bpf_headers/bpf_core_read.h [new file with mode: 0644]
ebpf_prog/bpf_headers/bpf_helper_defs.h [new file with mode: 0644]
ebpf_prog/bpf_headers/bpf_helpers.h [new file with mode: 0644]
ebpf_prog/bpf_headers/bpf_tracing.h [new file with mode: 0644]
ebpf_prog/common.h [new file with mode: 0644]
ebpf_prog/common_defs.h [new file with mode: 0644]
ebpf_prog/opensnitch-dns.c [new file with mode: 0644]
ebpf_prog/opensnitch-procs.c [new file with mode: 0644]
ebpf_prog/opensnitch.c [new file with mode: 0644]
proto/.gitignore [new file with mode: 0644]
proto/Makefile [new file with mode: 0644]
proto/ui.proto [new file with mode: 0644]
release.sh [new file with mode: 0755]
screenshots/opensnitch-ui-general-tab-deny.png [new file with mode: 0644]
screenshots/opensnitch-ui-proc-details.png [new file with mode: 0644]
screenshots/screenshot.png [new file with mode: 0644]
ui/.gitignore [new file with mode: 0644]
ui/LICENSE [new file with mode: 0644]
ui/MANIFEST.in [new file with mode: 0644]
ui/Makefile [new file with mode: 0644]
ui/bin/opensnitch-ui [new file with mode: 0755]
ui/i18n/Makefile [new file with mode: 0644]
ui/i18n/README.md [new file with mode: 0644]
ui/i18n/generate_i18n.sh [new file with mode: 0755]
ui/i18n/locales/de_DE/opensnitch-de_DE.ts [new file with mode: 0644]
ui/i18n/locales/es_ES/opensnitch-es_ES.ts [new file with mode: 0644]
ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts [new file with mode: 0644]
ui/i18n/locales/fi_FI/opensnitch-fi_FI.ts [new file with mode: 0644]
ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts [new file with mode: 0644]
ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts [new file with mode: 0644]
ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts [new file with mode: 0644]
ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts [new file with mode: 0644]
ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts [new file with mode: 0644]
ui/i18n/locales/nl_NL/opensnitch-nl_NL.ts [new file with mode: 0644]
ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts [new file with mode: 0644]
ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts [new file with mode: 0644]
ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts [new file with mode: 0644]
ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts [new file with mode: 0644]
ui/i18n/locales/zh_TW/opensnitch-zh_TW.ts [new file with mode: 0644]
ui/i18n/opensnitch_i18n.pro [new file with mode: 0644]
ui/opensnitch/__init__.py [new file with mode: 0644]
ui/opensnitch/actions/__init__.py [new file with mode: 0644]
ui/opensnitch/actions/default_configs.py [new file with mode: 0644]
ui/opensnitch/actions/highlight.py [new file with mode: 0644]
ui/opensnitch/actions/utils.py [new file with mode: 0644]
ui/opensnitch/auth/__init__.py [new file with mode: 0644]
ui/opensnitch/config.py [new file with mode: 0644]
ui/opensnitch/customwidgets/__init__.py [new file with mode: 0644]
ui/opensnitch/customwidgets/addresstablemodel.py [new file with mode: 0644]
ui/opensnitch/customwidgets/colorizeddelegate.py [new file with mode: 0644]
ui/opensnitch/customwidgets/firewalltableview.py [new file with mode: 0644]
ui/opensnitch/customwidgets/generictableview.py [new file with mode: 0644]
ui/opensnitch/customwidgets/main.py [new file with mode: 0644]
ui/opensnitch/customwidgets/updownbtndelegate.py [new file with mode: 0644]
ui/opensnitch/database/__init__.py [new file with mode: 0644]
ui/opensnitch/database/enums.py [new file with mode: 0644]
ui/opensnitch/database/migrations/upgrade_1.sql [new file with mode: 0644]
ui/opensnitch/database/migrations/upgrade_2.sql [new file with mode: 0644]
ui/opensnitch/database/migrations/upgrade_3.sql [new file with mode: 0644]
ui/opensnitch/desktop_parser.py [new file with mode: 0644]
ui/opensnitch/dialogs/__init__.py [new file with mode: 0644]
ui/opensnitch/dialogs/conndetails.py [new file with mode: 0644]
ui/opensnitch/dialogs/firewall.py [new file with mode: 0644]
ui/opensnitch/dialogs/firewall_rule.py [new file with mode: 0644]
ui/opensnitch/dialogs/preferences.py [new file with mode: 0644]
ui/opensnitch/dialogs/processdetails.py [new file with mode: 0644]
ui/opensnitch/dialogs/prompt.py [new file with mode: 0644]
ui/opensnitch/dialogs/ruleseditor.py [new file with mode: 0644]
ui/opensnitch/dialogs/stats.py [new file with mode: 0644]
ui/opensnitch/firewall/__init__.py [new file with mode: 0644]
ui/opensnitch/firewall/chains.py [new file with mode: 0644]
ui/opensnitch/firewall/enums.py [new file with mode: 0644]
ui/opensnitch/firewall/exprs.py [new file with mode: 0644]
ui/opensnitch/firewall/profiles.py [new file with mode: 0644]
ui/opensnitch/firewall/rules.py [new file with mode: 0644]
ui/opensnitch/firewall/utils.py [new file with mode: 0644]
ui/opensnitch/nodes.py [new file with mode: 0644]
ui/opensnitch/notifications.py [new file with mode: 0644]
ui/opensnitch/proto/__init__.py [new file with mode: 0644]
ui/opensnitch/proto/pre3200/ui_pb2.py [new file with mode: 0644]
ui/opensnitch/proto/pre3200/ui_pb2_grpc.py [new file with mode: 0644]
ui/opensnitch/proto/ui_pb2.py [new file with mode: 0644]
ui/opensnitch/proto/ui_pb2_grpc.py [new file with mode: 0644]
ui/opensnitch/res/__init__.py [new file with mode: 0644]
ui/opensnitch/res/firewall.ui [new file with mode: 0644]
ui/opensnitch/res/firewall_rule.ui [new file with mode: 0644]
ui/opensnitch/res/icon-alert.png [new file with mode: 0644]
ui/opensnitch/res/icon-off.png [new file with mode: 0644]
ui/opensnitch/res/icon-pause.png [new file with mode: 0644]
ui/opensnitch/res/icon-pause.svg [new file with mode: 0644]
ui/opensnitch/res/icon-red.png [new file with mode: 0644]
ui/opensnitch/res/icon-white.png [new file with mode: 0644]
ui/opensnitch/res/icon-white.svg [new file with mode: 0644]
ui/opensnitch/res/icon.png [new file with mode: 0644]
ui/opensnitch/res/preferences.ui [new file with mode: 0644]
ui/opensnitch/res/process_details.ui [new file with mode: 0644]
ui/opensnitch/res/prompt.ui [new file with mode: 0644]
ui/opensnitch/res/resources.qrc [new file with mode: 0644]
ui/opensnitch/res/ruleseditor.ui [new file with mode: 0644]
ui/opensnitch/res/stats.ui [new file with mode: 0644]
ui/opensnitch/rules.py [new file with mode: 0644]
ui/opensnitch/service.py [new file with mode: 0644]
ui/opensnitch/utils/__init__.py [new file with mode: 0644]
ui/opensnitch/utils/infowindow.py [new file with mode: 0644]
ui/opensnitch/utils/languages.py [new file with mode: 0644]
ui/opensnitch/utils/qvalidator.py [new file with mode: 0644]
ui/opensnitch/utils/xdg.py [new file with mode: 0644]
ui/opensnitch/version.py [new file with mode: 0644]
ui/requirements.txt [new file with mode: 0644]
ui/resources/icons/48x48/opensnitch-ui.png [new file with mode: 0644]
ui/resources/icons/64x64/opensnitch-ui.png [new file with mode: 0644]
ui/resources/icons/opensnitch-ui.svg [new file with mode: 0644]
ui/resources/io.github.evilsocket.opensnitch.appdata.xml [new file with mode: 0644]
ui/resources/kcm_opensnitch.desktop [new file with mode: 0644]
ui/resources/opensnitch_ui.desktop [new file with mode: 0644]
ui/setup.py [new file with mode: 0644]
ui/tests/README.md [new file with mode: 0644]
ui/tests/__init__.py [new file with mode: 0644]
ui/tests/dialogs/__init__.py [new file with mode: 0644]
ui/tests/dialogs/test_preferences.py [new file with mode: 0644]
ui/tests/dialogs/test_ruleseditor.py [new file with mode: 0644]
ui/tests/test_nodes.py [new file with mode: 0644]
utils/legacy/make_ads_rules.py [new file with mode: 0644]
utils/packaging/build_modules.sh [new file with mode: 0644]
utils/packaging/daemon/deb/debian/NEWS [new file with mode: 0644]
utils/packaging/daemon/deb/debian/changelog [new file with mode: 0644]
utils/packaging/daemon/deb/debian/control [new file with mode: 0644]
utils/packaging/daemon/deb/debian/copyright [new file with mode: 0644]
utils/packaging/daemon/deb/debian/gbp.conf [new file with mode: 0644]
utils/packaging/daemon/deb/debian/gitlab-ci.yml [new file with mode: 0644]
utils/packaging/daemon/deb/debian/opensnitch.init [new file with mode: 0644]
utils/packaging/daemon/deb/debian/opensnitch.install [new file with mode: 0644]
utils/packaging/daemon/deb/debian/opensnitch.logrotate [new file with mode: 0644]
utils/packaging/daemon/deb/debian/opensnitch.service [new file with mode: 0644]
utils/packaging/daemon/deb/debian/rules [new file with mode: 0755]
utils/packaging/daemon/deb/debian/source/format [new file with mode: 0644]
utils/packaging/daemon/deb/debian/watch [new file with mode: 0644]
utils/packaging/daemon/rpm/opensnitch.spec [new file with mode: 0644]
utils/packaging/ui/deb/debian/changelog [new file with mode: 0644]
utils/packaging/ui/deb/debian/compat [new file with mode: 0644]
utils/packaging/ui/deb/debian/control [new file with mode: 0644]
utils/packaging/ui/deb/debian/copyright [new file with mode: 0644]
utils/packaging/ui/deb/debian/postinst [new file with mode: 0755]
utils/packaging/ui/deb/debian/postrm [new file with mode: 0755]
utils/packaging/ui/deb/debian/rules [new file with mode: 0755]
utils/packaging/ui/deb/debian/source/format [new file with mode: 0644]
utils/packaging/ui/deb/debian/source/options [new file with mode: 0644]
utils/packaging/ui/rpm/opensnitch-ui.spec [new file with mode: 0644]
utils/scripts/ads/update_adlists.sh [new file with mode: 0755]
utils/scripts/debug-ebpf-maps.sh [new file with mode: 0644]
utils/scripts/restart-opensnitch-onsleep.sh [new file with mode: 0755]

diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644 (file)
index 0000000..815daa9
--- /dev/null
@@ -0,0 +1,12 @@
+# These are supported funding model platforms
+
+github: gustavo-iniguez-goya
+patreon: # Replace with a single patreon username
+open_collective: # Replace with a single Open Collective username
+ko_fi: # Replace with a single Ko-fi username
+tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
+community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
+liberapay: # Replace with a single Liberapay username
+issuehunt: # Replace with a single IssueHunt username
+otechie: # Replace with a single Otechie username
+custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644 (file)
index 0000000..3d2b6f3
--- /dev/null
@@ -0,0 +1,60 @@
+---
+name: 🐞 Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+Please, check the FAQ and Known Problems pages before creating the bug report:
+
+https://github.com/evilsocket/opensnitch/wiki/FAQs
+
+GUI related issues:
+https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems
+
+Daemon related issues:
+ - Run `opensnitchd -check-requirements` to see if your kernel is compatible.
+ - https://github.com/evilsocket/opensnitch/wiki/daemon-known-problems
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+Include the following information:
+ - OpenSnitch version.
+ - OS: [e.g. Debian GNU/Linux, ArchLinux, Slackware, ...]
+ - Version [e.g. Buster, 10.3, 20.04]
+ - Window Manager: [e.g. GNOME Shell, KDE, enlightenment, i3wm, ...]
+ - Kernel version: echo $(uname -a)
+
+**To Reproduce**
+Describe in detail as much as you can what happened.
+
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Post error logs:** 
+If it's a crash of the GUI: 
+ - Launch it from a terminal and reproduce the issue.
+ - Post the errors logged to the terminal.
+
+If the daemon doesn't start or doesn't intercept connections:
+ - Run `opensnitchd -check-requirements` to see if your kernel is compatible.
+ - Post last 15 lines of the log file `/var/log/opensnitchd.log`
+ - Or launch it from a terminal as root (`# /usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules`) and post the errors logged to the terminal.
+
+If the deb or rpm packages fail to install:
+ - Install them from a terminal (`$ sudo dpkg -i opensnitch*` / `$ sudo yum install opensnitch*`), and post the errors logged to stdout.
+
+**Expected behavior (optional)**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots or videos to help explain your problem. It may help to understand the issue much better.
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644 (file)
index 0000000..dd3c313
--- /dev/null
@@ -0,0 +1,4 @@
+contact_links:
+  - name: 🙋 Question
+    url: https://github.com/evilsocket/opensnitch/discussions/new
+    about: Ask your question here
diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md
new file mode 100644 (file)
index 0000000..7e54dce
--- /dev/null
@@ -0,0 +1,15 @@
+---
+name: 💡 Feature request
+about: Suggest an idea 
+title: '[Feature Request] <title>'
+labels: feature
+assignees: ''
+
+---
+
+<!--
+Note: Please, use the search box to see if this feature has already been requested.
+-->
+
+### Summary:
+<!-- A concise description of the new feature. -->
diff --git a/.github/workflows/build_ebpf_modules.yml b/.github/workflows/build_ebpf_modules.yml
new file mode 100644 (file)
index 0000000..1b65f69
--- /dev/null
@@ -0,0 +1,59 @@
+# This is a basic workflow to help you get started with Actions
+
+name: CI - build eBPF modules
+
+# Controls when the workflow will run
+on:
+  # Triggers the workflow on push or pull request events but only for the "master" branch
+  push:
+    paths:
+      - 'ebpf_prog/*'
+      - '.github/workflows/build_ebpf_modules.yml'
+  pull_request:
+    paths:
+      - 'ebpf_prog/*'
+      - '.github/workflows/build_ebpf_modules.yml'
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+
+  # This workflow contains a single job called "build"
+  # The matrix configuration will execute the steps, once per dimension defined:
+  # kernel 5.8 + tag 1.5.0
+  # kernel 5.8 + tag master
+  # kernel 6.0 + tag 1.5.0, etc
+  build:  
+    strategy:
+        matrix:
+          kernel: ["6.0"]
+          tag: ["1.6.0"]
+          
+    runs-on: ubuntu-22.04
+
+    steps:
+        - name: Check out code into the Go module directory
+          uses: actions/checkout@v3
+          with:
+            # ref: can be a branch name, tag, commit, etc
+            ref: ${{ matrix.tag }}
+      
+        - name: Get dependencies
+          run: |
+            sudo apt-get install git dpkg-dev rpm flex bison ca-certificates wget python3 rsync bc libssl-dev clang llvm libelf-dev libzip-dev git libnetfilter-queue-dev libpcap-dev protobuf-compiler python3-pip dh-golang golang-any golang-golang-x-net-dev golang-google-grpc-dev golang-goprotobuf-dev libmnl-dev golang-github-vishvananda-netlink-dev golang-github-evilsocket-ftrace-dev golang-github-google-gopacket-dev golang-github-fsnotify-fsnotify-dev linux-headers-$(uname -r)
+        - name: Download kernel sources and compile eBPF modules
+          run: |
+            kernel_version="${{ matrix.kernel }}"
+            if [ ! -d utils/packaging/ ]; then
+              mkdir -p utils/packaging/
+            fi
+            wget https://raw.githubusercontent.com/evilsocket/opensnitch/master/utils/packaging/build_modules.sh -O utils/packaging/build_modules.sh
+            bash utils/packaging/build_modules.sh $kernel_version
+            sha1sum ebpf_prog/modules/opensnitch*o > ebpf_prog/modules/checksums.txt
+
+        - uses: actions/upload-artifact@v4
+          with:
+            name: opensnitch-ebpf-modules-${{ matrix.kernel }}-${{ matrix.tag }}
+            path: ebpf_prog/modules/*
diff --git a/.github/workflows/generic_validations.yml b/.github/workflows/generic_validations.yml
new file mode 100644 (file)
index 0000000..a8cec13
--- /dev/null
@@ -0,0 +1,37 @@
+name: Test resources validation
+on:
+
+  # Trigger this workflow only when ebpf modules changes.
+  push:
+    paths:
+      - 'ui/resources/*'
+      - '.github/workflows/generic.yml'
+  pull_request:
+    paths:
+      - 'ui/resources/*'
+      - '.github/workflows/generic.yml'
+
+  # Allow to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+jobs:
+
+  build:
+    name: Install tools
+    runs-on: ubuntu-latest
+    steps:
+
+    - name: Check out git code
+      uses: actions/checkout@v2
+
+    - name: Get and prepare dependencies
+      run: |
+        set -e
+        set -x
+        sudo apt install desktop-file-utils appstream
+    - name: Validate resources
+      run: |
+        set -e
+        set -x
+        desktop-file-validate ui/resources/opensnitch_ui.desktop
+        appstreamcli validate ui/resources/io.github.evilsocket.opensnitch.appdata.xml
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
new file mode 100644 (file)
index 0000000..307b9d3
--- /dev/null
@@ -0,0 +1,49 @@
+name: Build status
+on:
+  push:
+    paths:
+      - 'daemon/**'
+      - '.github/workflows/go.yml'
+  pull_request:
+    paths:
+      - 'daemon/**'
+      - '.github/workflows/go.yml'
+
+  # Allows you to run this workflow manually from the Actions tab
+  workflow_dispatch:
+
+jobs:
+  build:
+    name: Build
+    runs-on: ubuntu-latest
+    steps:
+      - name: Set up Go 1.20.10
+        uses: actions/setup-go@v3
+        with:
+          go-version: 1.20.10
+        id: go
+
+      - name: Check out code into the Go module directory
+        uses: actions/checkout@v3
+
+      - name: Get dependencies
+        run: |
+          sudo apt-get install git libnetfilter-queue-dev libmnl-dev libpcap-dev protobuf-compiler
+          export GOPATH=~/go
+          export PATH=$PATH:$GOPATH/bin
+          go install github.com/golang/protobuf/protoc-gen-go@latest
+          go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1
+          go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0
+          cd proto
+          make ../daemon/ui/protocol/ui.pb.go
+          cd ../daemon
+          go mod tidy; go mod vendor
+
+      - name: Build
+        run: |
+          cd daemon
+          go build -v .
+      - name: Test
+        run: |
+          cd daemon
+          sudo PRIVILEGED_TESTS=1 NETLINK_TESTS=1 go test ./...
diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..8546be8
--- /dev/null
@@ -0,0 +1,3 @@
+*.sock
+*.pyc
+*.profile
diff --git a/LICENSE b/LICENSE
new file mode 100644 (file)
index 0000000..f288702
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..96d668c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,47 @@
+all: protocol opensnitch_daemon gui
+
+install:
+       @cd daemon && make install      
+       @cd ui && make install
+
+protocol:
+       @cd proto && make
+
+opensnitch_daemon:
+       @cd daemon && make
+
+gui:
+       @cd ui && make
+
+clean:
+       @cd daemon && make clean
+       @cd proto && make clean
+       @cd ui && make clean
+
+run:
+       cd ui && pip3 install --upgrade . && cd ..
+       opensnitch-ui --socket unix:///tmp/osui.sock &
+       ./daemon/opensnitchd -rules-path /etc/opensnitchd/rules -ui-socket unix:///tmp/osui.sock -cpu-profile cpu.profile -mem-profile mem.profile
+
+test: 
+       clear 
+       make clean
+       clear
+       mkdir -p rules
+       make 
+       clear
+       make run
+
+adblocker:
+       clear 
+       make clean
+       clear
+       make 
+       clear
+       python make_ads_rules.py
+       clear
+       cd ui && pip3 install --upgrade . && cd ..
+       opensnitch-ui --socket unix:///tmp/osui.sock &
+       ./daemon/opensnitchd -rules-path /etc/opensnitchd/rules -ui-socket unix:///tmp/osui.sock
+
+
diff --git a/README.md b/README.md
new file mode 100644 (file)
index 0000000..16d25c4
--- /dev/null
+++ b/README.md
@@ -0,0 +1,77 @@
+<p align="center">
+  <img alt="opensnitch" src="https://raw.githubusercontent.com/evilsocket/opensnitch/master/ui/opensnitch/res/icon.png" height="160" />
+  <p align="center">
+    <img src="https://github.com/evilsocket/opensnitch/workflows/Build%20status/badge.svg" />
+    <a href="https://github.com/evilsocket/opensnitch/releases/latest"><img alt="Release" src="https://img.shields.io/github/release/evilsocket/opensnitch.svg?style=flat-square"></a>
+    <a href="https://github.com/evilsocket/opensnitch/blob/master/LICENSE.md"><img alt="Software License" src="https://img.shields.io/badge/license-GPL3-brightgreen.svg?style=flat-square"></a>
+    <a href="https://goreportcard.com/report/github.com/evilsocket/opensnitch/daemon"><img alt="Go Report Card" src="https://goreportcard.com/badge/github.com/evilsocket/opensnitch/daemon?style=flat-square"></a>
+    <a href="https://repology.org/project/opensnitch/versions"><img src="https://repology.org/badge/tiny-repos/opensnitch.svg" alt="Packaging status"></a>
+  </p>
+</p>
+
+<p align="center"><strong>OpenSnitch</strong> is a GNU/Linux application firewall.</p>
+
+<p align="center">•• <a href="#key-features">Key Features</a> • <a href="#download">Download</a> • <a href="#installation">Installation</a> • <a href="#opensnitch-in-action">Usage examples</a> • <a href="#in-the-press">In the press</a> ••</p>
+
+<p align="center">
+  <img src="https://user-images.githubusercontent.com/2742953/85205382-6ba9cb00-b31b-11ea-8e9a-bd4b8b05a236.png" alt="OpenSnitch"/>
+</p>
+
+## Key features
+ * Interactive outbound connections filtering.
+ * [Block ads, trackers or malware domains](https://github.com/evilsocket/opensnitch/wiki/block-lists) system wide.
+ * Ability to [configure system firewall](https://github.com/evilsocket/opensnitch/wiki/System-rules) from the GUI (nftables).
+   - Configure input policy, allow inbound services, etc.
+ * Manage [multiple nodes](https://github.com/evilsocket/opensnitch/wiki/Nodes) from a centralized GUI.
+ * [SIEM integration](https://github.com/evilsocket/opensnitch/wiki/SIEM-integration)
+
+## Download
+
+Download deb/rpm packages for your system from https://github.com/evilsocket/opensnitch/releases
+
+## Installation
+
+#### deb
+> $ sudo apt install ./opensnitch*.deb ./python3-opensnitch-ui*.deb
+
+#### rpm
+> $ sudo yum localinstall opensnitch-1*.rpm; sudo yum localinstall opensnitch-ui*.rpm 
+
+Then run: `$ opensnitch-ui` or launch the GUI from the Applications menu.
+
+Please, refer to [the documentation](https://github.com/evilsocket/opensnitch/wiki/Installation) for detailed information.
+
+## OpenSnitch in action
+
+Examples of OpenSnitch intercepting unexpected connections:
+
+https://github.com/evilsocket/opensnitch/discussions/categories/show-and-tell
+
+Have you seen a connection you didn't expect? [submit it!](https://github.com/evilsocket/opensnitch/discussions/new?category=show-and-tell)
+
+## In the press
+
+- 2017 [PenTest Magazine](https://twitter.com/pentestmag/status/857321886807605248)
+- 11/2019 [It's Foss](https://itsfoss.com/opensnitch-firewall-linux/)
+- 03/2020 [Linux Format #232](https://www.linux-magazine.com/Issues/2020/232/Firewalld-and-OpenSnitch)
+- 08/2020 [Linux Magazine Polska #194](https://linux-magazine.pl/archiwum/wydanie/387)
+- 08/2021 [Linux Format #280](https://github.com/evilsocket/opensnitch/discussions/631)
+- 02/2022 [Linux User](https://www.linux-community.de/magazine/linuxuser/2022/03/)
+- 06/2022 [Linux Magazine #259](https://www.linux-magazine.com/Issues/2022/259/OpenSnitch)
+
+## Donations
+
+If you find OpenSnitch useful and want to donate to the dedicated developers, you can do it from the **Sponsor this project** section on the right side of this repository.
+
+You can see here who are the current maintainers of OpenSnitch:
+https://github.com/evilsocket/opensnitch/commits/master
+
+## Contributors
+
+[See the list](https://github.com/evilsocket/opensnitch/graphs/contributors)
+
+## Translating
+
+<a href="https://hosted.weblate.org/engage/opensnitch/">
+<img src="https://hosted.weblate.org/widgets/opensnitch/-/glossary/multi-auto.svg" alt="Translation status" />
+</a>
diff --git a/daemon/.gitignore b/daemon/.gitignore
new file mode 100644 (file)
index 0000000..ac08621
--- /dev/null
@@ -0,0 +1,2 @@
+opensnitchd
+vendor
diff --git a/daemon/Gopkg.toml b/daemon/Gopkg.toml
new file mode 100644 (file)
index 0000000..419b318
--- /dev/null
@@ -0,0 +1,19 @@
+[[constraint]]
+  name = "github.com/fsnotify/fsnotify"
+  version = "1.4.7"
+
+[[constraint]]
+  name = "github.com/google/gopacket"
+  version = "~1.1.14"
+
+[[constraint]]
+  name = "google.golang.org/grpc"
+  version = "~1.11.2"
+
+[[constraint]]
+  name = "github.com/evilsocket/ftrace"
+  version = "~1.2.0"
+
+[prune]
+  go-tests = true
+  unused-packages = true
diff --git a/daemon/Makefile b/daemon/Makefile
new file mode 100644 (file)
index 0000000..df537b3
--- /dev/null
@@ -0,0 +1,26 @@
+#SRC contains all *.go *.c *.h files in daemon/ and its subfolders 
+SRC := $(shell find . -type f -name '*.go' -o -name '*.h' -o -name '*.c')
+PREFIX?=/usr/local
+
+all: opensnitchd
+
+install:
+       @mkdir -p $(DESTDIR)/etc/opensnitchd/rules
+       @install -Dm755 opensnitchd \
+               -t $(DESTDIR)$(PREFIX)/bin/
+       @install -Dm644 opensnitchd.service \
+               -t $(DESTDIR)/etc/systemd/system/
+       @install -Dm644 default-config.json \
+               -t $(DESTDIR)/etc/opensnitchd/
+       @install -Dm644 system-fw.json \
+               -t $(DESTDIR)/etc/opensnitchd/
+       @systemctl daemon-reload
+
+opensnitchd: $(SRC)
+       @go get
+       @go build -o opensnitchd . 
+
+clean:
+       @rm -rf opensnitchd
+
+
diff --git a/daemon/conman/connection.go b/daemon/conman/connection.go
new file mode 100644 (file)
index 0000000..2f6dcf7
--- /dev/null
@@ -0,0 +1,332 @@
+package conman
+
+import (
+       "errors"
+       "fmt"
+       "net"
+       "os"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/dns"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/netfilter"
+       "github.com/evilsocket/opensnitch/daemon/netlink"
+       "github.com/evilsocket/opensnitch/daemon/netstat"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       "github.com/evilsocket/opensnitch/daemon/procmon/audit"
+       "github.com/evilsocket/opensnitch/daemon/procmon/ebpf"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+
+       "github.com/google/gopacket/layers"
+)
+
+// Connection represents an outgoing connection.
+type Connection struct {
+       Entry    *netstat.Entry
+       Process  *procmon.Process
+       Pkt      *netfilter.Packet
+       Protocol string
+       DstHost  string
+       SrcIP    net.IP
+       DstIP    net.IP
+       SrcPort  uint
+       DstPort  uint
+}
+
+var showUnknownCons = false
+
+// Parse extracts the IP layers from a network packet to determine what
+// process generated a connection.
+func Parse(nfp netfilter.Packet, interceptUnknown bool) *Connection {
+       showUnknownCons = interceptUnknown
+
+       if nfp.IsIPv4() {
+               con, err := NewConnection(&nfp)
+               if err != nil {
+                       log.Debug("%s", err)
+                       return nil
+               } else if con == nil {
+                       return nil
+               }
+               return con
+       }
+
+       if core.IPv6Enabled == false {
+               return nil
+       }
+       con, err := NewConnection6(&nfp)
+       if err != nil {
+               log.Debug("%s", err)
+               return nil
+       } else if con == nil {
+               return nil
+       }
+       return con
+
+}
+
+func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) (cr *Connection, err error) {
+       // no errors but not enough info neither
+       if c.parseDirection(protoType) == false {
+               return nil, nil
+       }
+       log.Debug("new connection %s => %d:%v -> %v (%s):%d uid: %d, mark: %x", c.Protocol, c.SrcPort, c.SrcIP, c.DstIP, c.DstHost, c.DstPort, nfp.UID, nfp.Mark)
+
+       c.Entry = &netstat.Entry{
+               Proto:   c.Protocol,
+               SrcIP:   c.SrcIP,
+               SrcPort: c.SrcPort,
+               DstIP:   c.DstIP,
+               DstPort: c.DstPort,
+               UserId:  -1,
+               INode:   -1,
+       }
+
+       pid := -1
+       uid := -1
+       if procmon.MethodIsEbpf() {
+               swap := false
+               c.Process, swap, err = ebpf.GetPid(c.Protocol, c.SrcPort, c.SrcIP, c.DstIP, c.DstPort)
+               if swap {
+                       c.swapFields()
+               }
+
+               if c.Process != nil {
+                       c.Entry.UserId = c.Process.UID
+                       return c, nil
+               }
+               if err != nil {
+                       log.Debug("ebpf warning: %v", err)
+                       return nil, nil
+               }
+       } else if procmon.MethodIsAudit() {
+               if aevent := audit.GetEventByPid(pid); aevent != nil {
+                       audit.Lock.RLock()
+                       c.Process = procmon.NewProcess(pid, aevent.ProcName)
+                       c.Process.Path = aevent.ProcPath
+                       c.Process.ReadCmdline()
+                       c.Process.CWD = aevent.ProcDir
+                       audit.Lock.RUnlock()
+                       // if the proc dir contains non alhpa-numeric chars the field is empty
+                       if c.Process.CWD == "" {
+                               c.Process.ReadCwd()
+                       }
+                       c.Process.ReadEnv()
+                       c.Process.CleanPath()
+
+                       procmon.AddToActivePidsCache(uint64(pid), c.Process)
+                       return c, nil
+               }
+       }
+
+       // Sometimes when using eBPF, the PID is not found by the connection's parameters,
+       // but falling back to legacy methods helps to find it and avoid "unknown/kernel pop-ups".
+       //
+       // One of the reasons is because after coming back from suspend state, for some reason (bug?),
+       // gobpf/libbpf is unable to delete ebpf map entries, so when they reach the maximum capacity no
+       // more entries are added, nor updated.
+       if pid < 0 {
+               // 0. lookup uid and inode via netlink. Can return several inodes.
+               // 1. lookup uid and inode using /proc/net/(udp|tcp|udplite)
+               // 2. lookup pid by inode
+               // 3. if this is coming from us, just accept
+               // 4. lookup process info by pid
+               var inodeList []int
+               uid, inodeList = netlink.GetSocketInfo(c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort)
+               if len(inodeList) == 0 {
+                       procmon.GetInodeFromNetstat(c.Entry, &inodeList, c.Protocol, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort)
+               }
+
+               for n, inode := range inodeList {
+                       pid = procmon.GetPIDFromINode(inode, fmt.Sprint(inode, c.SrcIP, c.SrcPort, c.DstIP, c.DstPort))
+                       if pid != -1 {
+                               log.Debug("[%d] PID found %d [%d]", n, pid, inode)
+                               c.Entry.INode = inode
+                               break
+                       }
+               }
+       }
+
+       if pid == os.Getpid() {
+               // return a Process object with our PID, to be able to exclude our own connections
+               // (to the UI on a local socket for example)
+               c.Process = procmon.NewProcess(pid, "")
+               return c, nil
+       }
+
+       if nfp.UID != 0xffffffff {
+               uid = int(nfp.UID)
+       }
+       c.Entry.UserId = uid
+
+       if c.Process == nil {
+               if c.Process = procmon.FindProcess(pid, showUnknownCons); c.Process == nil {
+                       return nil, fmt.Errorf("Could not find process by its pid %d for: %s", pid, c)
+               }
+       }
+
+       return c, nil
+}
+
+// NewConnection creates a new Connection object, and returns the details of it.
+func NewConnection(nfp *netfilter.Packet) (c *Connection, err error) {
+       ipv4 := nfp.Packet.Layer(layers.LayerTypeIPv4)
+       if ipv4 == nil {
+               return nil, errors.New("Error getting IPv4 layer")
+       }
+       ip, ok := ipv4.(*layers.IPv4)
+       if !ok {
+               return nil, errors.New("Error getting IPv4 layer data")
+       }
+       c = &Connection{
+               SrcIP:   ip.SrcIP,
+               DstIP:   ip.DstIP,
+               DstHost: dns.HostOr(ip.DstIP, ""),
+               Pkt:     nfp,
+       }
+       return newConnectionImpl(nfp, c, "")
+}
+
+// NewConnection6 creates a IPv6 new Connection object, and returns the details of it.
+func NewConnection6(nfp *netfilter.Packet) (c *Connection, err error) {
+       ipv6 := nfp.Packet.Layer(layers.LayerTypeIPv6)
+       if ipv6 == nil {
+               return nil, errors.New("Error getting IPv6 layer")
+       }
+       ip, ok := ipv6.(*layers.IPv6)
+       if !ok {
+               return nil, errors.New("Error getting IPv6 layer data")
+       }
+       c = &Connection{
+               SrcIP:   ip.SrcIP,
+               DstIP:   ip.DstIP,
+               DstHost: dns.HostOr(ip.DstIP, ""),
+               Pkt:     nfp,
+       }
+       return newConnectionImpl(nfp, c, "6")
+}
+
+func (c *Connection) parseDirection(protoType string) bool {
+       ret := false
+       if tcpLayer := c.Pkt.Packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
+               if tcp, ok := tcpLayer.(*layers.TCP); ok == true && tcp != nil {
+                       c.Protocol = "tcp" + protoType
+                       c.DstPort = uint(tcp.DstPort)
+                       c.SrcPort = uint(tcp.SrcPort)
+                       ret = true
+
+                       if tcp.DstPort == 53 {
+                               c.getDomains(c.Pkt, c)
+                       }
+               }
+       } else if udpLayer := c.Pkt.Packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
+               if udp, ok := udpLayer.(*layers.UDP); ok == true && udp != nil {
+                       c.Protocol = "udp" + protoType
+                       c.DstPort = uint(udp.DstPort)
+                       c.SrcPort = uint(udp.SrcPort)
+                       ret = true
+
+                       if udp.DstPort == 53 {
+                               c.getDomains(c.Pkt, c)
+                       }
+               }
+       } else if udpliteLayer := c.Pkt.Packet.Layer(layers.LayerTypeUDPLite); udpliteLayer != nil {
+               if udplite, ok := udpliteLayer.(*layers.UDPLite); ok == true && udplite != nil {
+                       c.Protocol = "udplite" + protoType
+                       c.DstPort = uint(udplite.DstPort)
+                       c.SrcPort = uint(udplite.SrcPort)
+                       ret = true
+               }
+       } else if sctpLayer := c.Pkt.Packet.Layer(layers.LayerTypeSCTP); sctpLayer != nil {
+               if sctp, ok := sctpLayer.(*layers.SCTP); ok == true && sctp != nil {
+                       c.Protocol = "sctp" + protoType
+                       c.DstPort = uint(sctp.DstPort)
+                       c.SrcPort = uint(sctp.SrcPort)
+                       ret = true
+               }
+       } else if icmpLayer := c.Pkt.Packet.Layer(layers.LayerTypeICMPv4); icmpLayer != nil {
+               if icmp, ok := icmpLayer.(*layers.ICMPv4); ok == true && icmp != nil {
+                       c.Protocol = "icmp"
+                       c.DstPort = 0
+                       c.SrcPort = 0
+                       ret = true
+               }
+       } else if icmp6Layer := c.Pkt.Packet.Layer(layers.LayerTypeICMPv6); icmp6Layer != nil {
+               if icmp6, ok := icmp6Layer.(*layers.ICMPv6); ok == true && icmp6 != nil {
+                       c.Protocol = "icmp" + protoType
+                       c.DstPort = 0
+                       c.SrcPort = 0
+                       ret = true
+               }
+       }
+
+       return ret
+}
+
+// swapFields swaps connection's fields.
+// Used to workaround an issue where outbound connections
+// have the fields swapped (procmon/ebpf/find.go).
+func (c *Connection) swapFields() {
+       oEntry := c.Entry
+       c.Entry = &netstat.Entry{
+               Proto:   c.Protocol,
+               SrcIP:   oEntry.DstIP,
+               DstIP:   oEntry.SrcIP,
+               SrcPort: oEntry.DstPort,
+               DstPort: oEntry.SrcPort,
+               UserId:  oEntry.UserId,
+               INode:   oEntry.INode,
+       }
+       c.SrcIP = oEntry.DstIP
+       c.DstIP = oEntry.SrcIP
+       c.DstPort = oEntry.SrcPort
+       c.SrcPort = oEntry.DstPort
+}
+
+func (c *Connection) getDomains(nfp *netfilter.Packet, con *Connection) {
+       domains := dns.GetQuestions(nfp)
+       if len(domains) < 1 {
+               return
+       }
+       for _, dns := range domains {
+               con.DstHost = dns
+       }
+}
+
+// To returns the destination host of a connection.
+func (c *Connection) To() string {
+       if c.DstHost == "" {
+               return c.DstIP.String()
+       }
+       return fmt.Sprintf("%s (%s)", c.DstHost, c.DstIP)
+}
+
+func (c *Connection) String() string {
+       if c.Entry == nil {
+               return fmt.Sprintf("%d:%s ->(%s)-> %s:%d", c.SrcPort, c.SrcIP, c.Protocol, c.To(), c.DstPort)
+       }
+
+       if c.Process == nil {
+               return fmt.Sprintf("%d:%s (uid:%d) ->(%s)-> %s:%d", c.SrcPort, c.SrcIP, c.Entry.UserId, c.Protocol, c.To(), c.DstPort)
+       }
+
+       return fmt.Sprintf("%s (%d) -> %s:%d (proto:%s uid:%d)", c.Process.Path, c.Process.ID, c.To(), c.DstPort, c.Protocol, c.Entry.UserId)
+}
+
+// Serialize returns a connection serialized.
+func (c *Connection) Serialize() *protocol.Connection {
+       return &protocol.Connection{
+               Protocol:    c.Protocol,
+               SrcIp:       c.SrcIP.String(),
+               SrcPort:     uint32(c.SrcPort),
+               DstIp:       c.DstIP.String(),
+               DstHost:     c.DstHost,
+               DstPort:     uint32(c.DstPort),
+               UserId:      uint32(c.Entry.UserId),
+               ProcessId:   uint32(c.Process.ID),
+               ProcessPath: c.Process.Path,
+               ProcessArgs: c.Process.Args,
+               ProcessEnv:  c.Process.Env,
+               ProcessCwd:  c.Process.CWD,
+       }
+}
diff --git a/daemon/conman/connection_test.go b/daemon/conman/connection_test.go
new file mode 100644 (file)
index 0000000..4c76a1a
--- /dev/null
@@ -0,0 +1,127 @@
+package conman
+
+import (
+       "fmt"
+       "net"
+       "testing"
+
+       "github.com/google/gopacket"
+       "github.com/google/gopacket/layers"
+
+       "github.com/evilsocket/opensnitch/daemon/netfilter"
+)
+
+// Adding new packets:
+// wireshark -> right click -> Copy as HexDump -> create []byte{}
+
+func NewTCPPacket() gopacket.Packet {
+       // 47676:192.168.1.100 -> 1.1.1.1:23
+       testTCPPacket := []byte{0x4c, 0x6e, 0x6e, 0xd5, 0x79, 0xbf, 0x00, 0x28, 0x9d, 0x43, 0x7f, 0xd7, 0x08, 0x00, 0x45, 0x10,
+               0x00, 0x3c, 0x1d, 0x07, 0x40, 0x00, 0x40, 0x06, 0x59, 0x8e, 0xc0, 0xa8, 0x01, 0x6d, 0x01, 0x01,
+               0x01, 0x01, 0xba, 0x3c, 0x00, 0x17, 0x47, 0x7e, 0xf3, 0x0b, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02,
+               0xfa, 0xf0, 0x4c, 0x27, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, 0x08, 0x0a, 0x91, 0xfb,
+               0xb5, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x03, 0x0a}
+       return gopacket.NewPacket(testTCPPacket, layers.LinkTypeEthernet, gopacket.Default)
+}
+
+func NewUDPPacket() gopacket.Packet {
+       // 29517:192.168.1.109 -> 1.0.0.1:53
+       testUDPPacketDNS := []byte{
+               0x4c, 0x6e, 0x6e, 0xd5, 0x79, 0xbf, 0x00, 0x28, 0x9d, 0x43, 0x7f, 0xd7, 0x08, 0x00, 0x45, 0x00,
+               0x00, 0x40, 0x54, 0x1a, 0x40, 0x00, 0x3f, 0x11, 0x24, 0x7d, 0xc0, 0xa8, 0x01, 0x6d, 0x01, 0x00,
+               0x00, 0x01, 0x73, 0x4d, 0x00, 0x35, 0x00, 0x2c, 0xf1, 0x17, 0x05, 0x51, 0x00, 0x20, 0x00, 0x01,
+               0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x70, 0x69, 0x04, 0x68, 0x6f, 0x6c, 0x65, 0x00, 0x00,
+               0x01, 0x00, 0x01, 0x00, 0x00, 0x29, 0x10, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00,
+       }
+
+       return gopacket.NewPacket(testUDPPacketDNS, layers.LinkTypeEthernet, gopacket.Default)
+}
+
+func EstablishConnection(proto, dst string) (net.Conn, error) {
+       c, err := net.Dial(proto, dst)
+       if err != nil {
+               fmt.Println(err)
+               return nil, err
+       }
+       return c, nil
+}
+
+func ListenOnPort(proto, port string) (net.Listener, error) {
+       l, err := net.Listen(proto, port)
+       if err != nil {
+               fmt.Println(err)
+               return nil, err
+       }
+       return l, nil
+}
+
+func NewPacket(pkt gopacket.Packet) *netfilter.Packet {
+       return &netfilter.Packet{
+               Packet:          pkt,
+               UID:             666,
+               NetworkProtocol: netfilter.IPv4,
+       }
+}
+
+func NewDummyConnection(src, dst net.IP) *Connection {
+       return &Connection{
+               SrcIP: src,
+               DstIP: dst,
+       }
+}
+
+// Test TCP parseDirection()
+func TestParseTCPDirection(t *testing.T) {
+       srcIP := net.IP{192, 168, 1, 100}
+       dstIP := net.IP{1, 1, 1, 1}
+       c := NewDummyConnection(srcIP, dstIP)
+       // 47676:192.168.1.100 -> 1.1.1.1:23
+       pkt := NewPacket(NewTCPPacket())
+       c.Pkt = pkt
+
+       // parseDirection extracts the src and dst port from a network packet.
+       if c.parseDirection("") == false {
+               t.Error("parseDirection() should not be false")
+               t.Fail()
+       }
+       if c.SrcPort != 47676 {
+               t.Error("parseDirection() SrcPort mismatch:", c)
+               t.Fail()
+       }
+       if c.DstPort != 23 {
+               t.Error("parseDirection() DstPort mismatch:", c)
+               t.Fail()
+       }
+       if c.Protocol != "tcp" {
+               t.Error("parseDirection() Protocol mismatch:", c)
+               t.Fail()
+       }
+}
+
+// Test UDP parseDirection()
+func TestParseUDPDirection(t *testing.T) {
+       srcIP := net.IP{192, 168, 1, 100}
+       dstIP := net.IP{1, 0, 0, 1}
+       c := NewDummyConnection(srcIP, dstIP)
+       // 29517:192.168.1.109 -> 1.0.0.1:53
+       pkt := NewPacket(NewUDPPacket())
+       c.Pkt = pkt
+
+       // parseDirection extracts the src and dst port from a network packet.
+       if c.parseDirection("") == false {
+               t.Error("parseDirection() should not be false")
+               t.Fail()
+       }
+       if c.SrcPort != 29517 {
+               t.Error("parseDirection() SrcPort mismatch:", c)
+               t.Fail()
+       }
+       if c.DstPort != 53 {
+               t.Error("parseDirection() DstPort mismatch:", c)
+               t.Fail()
+       }
+       if c.Protocol != "udp" {
+               t.Error("parseDirection() Protocol mismatch:", c)
+               t.Fail()
+       }
+}
diff --git a/daemon/core/core.go b/daemon/core/core.go
new file mode 100644 (file)
index 0000000..2c15ab5
--- /dev/null
@@ -0,0 +1,78 @@
+package core
+
+import (
+       "fmt"
+       "os"
+       "os/exec"
+       "os/user"
+       "path/filepath"
+       "strings"
+       "time"
+)
+
+const (
+       defaultTrimSet = "\r\n\t "
+)
+
+// Trim remove trailing spaces from a string.
+func Trim(s string) string {
+       return strings.Trim(s, defaultTrimSet)
+}
+
+// Exec spawns a new process and reurns the output.
+func Exec(executable string, args []string) (string, error) {
+       path, err := exec.LookPath(executable)
+       if err != nil {
+               return "", err
+       }
+
+       raw, err := exec.Command(path, args...).CombinedOutput()
+       if err != nil {
+               return "", err
+       }
+       return Trim(string(raw)), nil
+}
+
+// Exists checks if a path exists.
+func Exists(path string) bool {
+       if _, err := os.Stat(path); os.IsNotExist(err) {
+               return false
+       }
+       return true
+}
+
+// ExpandPath replaces '~' shorthand with the user's home directory.
+func ExpandPath(path string) (string, error) {
+       // Check if path is empty
+       if path != "" {
+               if strings.HasPrefix(path, "~") {
+                       usr, err := user.Current()
+                       if err != nil {
+                               return "", err
+                       }
+                       // Replace only the first occurrence of ~
+                       path = strings.Replace(path, "~", usr.HomeDir, 1)
+               }
+               return filepath.Abs(path)
+       }
+       return "", nil
+}
+
+// IsAbsPath verifies if a path is absolute or not
+func IsAbsPath(path string) bool {
+       return path[0] == 47 // 47 == '/'
+}
+
+// GetFileModTime checks if a file has been modified.
+func GetFileModTime(filepath string) (time.Time, error) {
+       fi, err := os.Stat(filepath)
+       if err != nil || fi.IsDir() {
+               return time.Now(), fmt.Errorf("GetFileModTime() Invalid file")
+       }
+       return fi.ModTime(), nil
+}
+
+// ConcatStrings joins the provided strings.
+func ConcatStrings(args ...string) string {
+       return strings.Join(args, "")
+}
diff --git a/daemon/core/ebpf.go b/daemon/core/ebpf.go
new file mode 100644 (file)
index 0000000..c63a4e7
--- /dev/null
@@ -0,0 +1,49 @@
+package core
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/iovisor/gobpf/elf"
+)
+
+// LoadEbpfModule loads the given eBPF module, from the given path if specified.
+// Otherwise t'll try to load the module from several default paths.
+func LoadEbpfModule(module, path string) (m *elf.Module, err error) {
+       var (
+               modulesDir = "/opensnitchd/ebpf"
+               paths      = []string{
+                       fmt.Sprint("/usr/local/lib", modulesDir),
+                       fmt.Sprint("/usr/lib", modulesDir),
+                       fmt.Sprint("/etc/opensnitchd"), // Deprecated: will be removed in future versions.
+               }
+       )
+
+       // if path has been specified, try to load the module from there.
+       if path != "" {
+               paths = []string{path}
+       }
+
+       modulePath := ""
+       moduleError := fmt.Errorf(`Module not found (%s) in any of the paths.
+You may need to install the corresponding package`, module)
+
+       for _, p := range paths {
+               modulePath = fmt.Sprint(p, "/", module)
+               log.Debug("[eBPF] trying to load %s", modulePath)
+               if !Exists(modulePath) {
+                       continue
+               }
+               m = elf.NewModule(modulePath)
+
+               if m.Load(nil) == nil {
+                       log.Info("[eBPF] module loaded: %s", modulePath)
+                       return m, nil
+               }
+               moduleError = fmt.Errorf(`
+unable to load eBPF module (%s). Your kernel version (%s) might not be compatible.
+If this error persists, change process monitor method to 'proc'`, module, GetKernelVersion())
+       }
+
+       return m, moduleError
+}
diff --git a/daemon/core/gzip.go b/daemon/core/gzip.go
new file mode 100644 (file)
index 0000000..34bb419
--- /dev/null
@@ -0,0 +1,28 @@
+package core
+
+import (
+       "compress/gzip"
+       "io/ioutil"
+       "os"
+)
+
+// ReadGzipFile reads a gzip to text.
+func ReadGzipFile(filename string) ([]byte, error) {
+       fd, err := os.Open(filename)
+       if err != nil {
+               return nil, err
+       }
+       defer fd.Close()
+
+       gz, err := gzip.NewReader(fd)
+       if err != nil {
+               return nil, err
+       }
+       defer gz.Close()
+
+       s, err := ioutil.ReadAll(gz)
+       if err != nil {
+               return nil, err
+       }
+       return s, nil
+}
diff --git a/daemon/core/system.go b/daemon/core/system.go
new file mode 100644 (file)
index 0000000..f43637f
--- /dev/null
@@ -0,0 +1,198 @@
+package core
+
+import (
+       "encoding/json"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "regexp"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+var (
+       // IPv6Enabled indicates if IPv6 protocol is enabled in the system
+       IPv6Enabled = Exists("/proc/sys/net/ipv6")
+)
+
+// GetHostname returns the name of the host where the daemon is running.
+func GetHostname() string {
+       hostname, _ := ioutil.ReadFile("/proc/sys/kernel/hostname")
+       return strings.Replace(string(hostname), "\n", "", -1)
+}
+
+// GetKernelVersion returns the kernel version.
+func GetKernelVersion() string {
+       version, _ := ioutil.ReadFile("/proc/sys/kernel/osrelease")
+       return strings.Replace(string(version), "\n", "", -1)
+}
+
+// CheckSysRequirements checks system features we need to work properly
+func CheckSysRequirements() {
+       type checksT struct {
+               RegExps []string
+               Reason  string
+       }
+       type ReqsList struct {
+               Item   string
+               Checks checksT
+       }
+       kVer := GetKernelVersion()
+
+       log.Raw("\n\t%sChecking system requirements for kernel version %s%s\n", log.FG_WHITE+log.BG_LBLUE, kVer, log.RESET)
+       log.Raw("%s------------------------------------------------------------------------------%s\n\n", log.FG_WHITE+log.BG_LBLUE, log.RESET)
+
+       confPaths := []string{
+               fmt.Sprint("/boot/config-", kVer),
+               "/proc/config.gz",
+               // Fedora SilverBlue
+               fmt.Sprint("/usr/lib/modules/", kVer, "/config"),
+       }
+
+       var fileContent []byte
+       var err error
+       for _, confFile := range confPaths {
+               if !Exists(confFile) {
+                       err = fmt.Errorf("%s not found", confFile)
+                       log.Debug(err.Error())
+                       continue
+               }
+
+               if confFile[len(confFile)-2:] == "gz" {
+                       fileContent, err = ReadGzipFile(confFile)
+               } else {
+                       fileContent, err = ioutil.ReadFile(confFile)
+               }
+               if err == nil {
+                       break
+               }
+       }
+       if err != nil {
+               fmt.Printf("\n\t%s kernel config not found (%s) in any of the expected paths.\n", log.Bold(log.Red("✘")), kVer)
+               fmt.Printf("\tPlease, open a new issue on github specifying your kernel and distro version (/etc/os-release).\n\n")
+               return
+       }
+
+       // TODO: check loaded/configured modules (nfnetlink, nfnetlink_queue, xt_NFQUEUE, etc)
+       // Other items to check:
+       // CONFIG_NETFILTER_NETLINK
+       // CONFIG_NETFILTER_NETLINK_QUEUE
+       const reqsList = `
+[
+{
+    "Item": "kprobes",
+    "Checks": {
+        "Regexps": [
+            "CONFIG_KPROBES=y",
+            "CONFIG_KPROBES_ON_FTRACE=y",
+            "CONFIG_HAVE_KPROBES=y",
+            "CONFIG_HAVE_KPROBES_ON_FTRACE=y",
+            "CONFIG_KPROBE_EVENTS=y"
+            ],
+        "Reason": " - KPROBES not fully supported by this kernel."
+    }
+},
+{
+    "Item": "uprobes",
+    "Checks": {
+        "Regexps": [
+            "CONFIG_UPROBES=y",
+            "CONFIG_UPROBE_EVENTS=y"
+            ],
+        "Reason": " * UPROBES not supported. Common error => cannot open uprobe_events: open /sys/kernel/debug/tracing/uprobe_events"
+    }
+},
+{
+    "Item": "ftrace",
+    "Checks": {
+        "Regexps": [
+            "CONFIG_FTRACE=y"
+            ],
+        "Reason": " - CONFIG_TRACE=y not set. Common error => Error while loading kprobes: invalid argument."
+    }
+},
+{
+    "Item": "syscalls",
+    "Checks": {
+        "Regexps": [
+            "CONFIG_HAVE_SYSCALL_TRACEPOINTS=y",
+            "CONFIG_FTRACE_SYSCALLS=y"
+            ],
+        "Reason": " - CONFIG_FTRACE_SYSCALLS or CONFIG_HAVE_SYSCALL_TRACEPOINTS not set. Common error => error enabling tracepoint tracepoint/syscalls/sys_enter_execve: cannot read tracepoint id"
+    }
+},
+{
+    "Item": "nfqueue",
+    "Checks": {
+        "Regexps": [
+                       "CONFIG_NETFILTER_NETLINK_QUEUE=[my]",
+                       "CONFIG_NFT_QUEUE=[my]",
+            "CONFIG_NETFILTER_XT_TARGET_NFQUEUE=[my]"
+            ],
+        "Reason": " * NFQUEUE netfilter extensions not supported by this kernel (CONFIG_NETFILTER_NETLINK_QUEUE, CONFIG_NFT_QUEUE, CONFIG_NETFILTER_XT_TARGET_NFQUEUE)."
+    }
+},
+{
+    "Item": "netlink",
+    "Checks": {
+        "Regexps": [
+                       "CONFIG_NETFILTER_NETLINK=[my]",
+                       "CONFIG_NETFILTER_NETLINK_QUEUE=[my]",
+                       "CONFIG_NETFILTER_NETLINK_ACCT=[my]"
+            ],
+        "Reason": " * NETLINK extensions not supported by this kernel (CONFIG_NETFILTER_NETLINK, CONFIG_NETFILTER_NETLINK_QUEUE, CONFIG_NETFILTER_NETLINK_ACCT)."
+    }
+},
+{
+    "Item": "net diagnostics",
+    "Checks": {
+        "Regexps": [
+                       "CONFIG_INET_DIAG=[my]",
+                       "CONFIG_INET_TCP_DIAG=[my]",
+                       "CONFIG_INET_UDP_DIAG=[my]",
+                       "CONFIG_INET_DIAG_DESTROY=[my]"
+            ],
+        "Reason": " * One or more socket monitoring interfaces are not enabled (CONFIG_INET_DIAG, CONFIG_INET_TCP_DIAG, CONFIG_INET_UDP_DIAG, CONFIG_DIAG_DESTROY (Reject feature))."
+    }
+}
+]
+`
+
+       reqsFullfiled := true
+       dec := json.NewDecoder(strings.NewReader(reqsList))
+       for {
+               var reqs []ReqsList
+               if err := dec.Decode(&reqs); err == io.EOF {
+                       break
+               } else if err != nil {
+                       log.Error("%s", err)
+                       break
+               }
+               for _, req := range reqs {
+                       checkOk := true
+                       for _, trex := range req.Checks.RegExps {
+                               fmt.Printf("\tChecking => %s\n", trex)
+                               re, err := regexp.Compile(trex)
+                               if err != nil {
+                                       fmt.Printf("\t%s %s\n", log.Bold(log.Red("Invalid regexp =>")), log.Red(trex))
+                                       continue
+                               }
+                               if re.Find(fileContent) == nil {
+                                       fmt.Printf("\t%s\n", log.Red(req.Checks.Reason))
+                                       checkOk = false
+                               }
+                       }
+                       if checkOk {
+                               fmt.Printf("\n\t* %s\t %s\n", log.Bold(log.Green(req.Item)), log.Bold(log.Green("✔")))
+                       } else {
+                               reqsFullfiled = false
+                               fmt.Printf("\n\t* %s\t %s\n", log.Bold(log.Red(req.Item)), log.Bold(log.Red("✘")))
+                       }
+                       fmt.Println()
+               }
+       }
+       if !reqsFullfiled {
+               log.Raw("\n%sWARNING:%s Your kernel doesn't support some of the features OpenSnitch needs:\nRead more: https://github.com/evilsocket/opensnitch/issues/774\n", log.FG_WHITE+log.BG_YELLOW, log.RESET)
+       }
+}
diff --git a/daemon/core/version.go b/daemon/core/version.go
new file mode 100644 (file)
index 0000000..561b1f6
--- /dev/null
@@ -0,0 +1,9 @@
+package core
+
+// version related consts
+const (
+       Name    = "opensnitch-daemon"
+       Version = "1.6.9"
+       Author  = "Simone 'evilsocket' Margaritelli"
+       Website = "https://github.com/evilsocket/opensnitch"
+)
diff --git a/daemon/data/rules/000-allow-localhost.json b/daemon/data/rules/000-allow-localhost.json
new file mode 100644 (file)
index 0000000..a8320b2
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "created": "2023-07-05T10:46:47.904024069+01:00",
+  "updated": "2023-07-05T10:46:47.921828104+01:00",
+  "name": "000-allow-localhost",
+  "description": "Allow connections to localhost. See this link for more information:\nhttps://github.com/evilsocket/opensnitch/wiki/Rules#localhost-connections",
+  "enabled": true,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "regexp",
+    "operand": "dest.ip",
+    "sensitive": false,
+    "data": "^(127\\.0\\.0\\.1|::1)$",
+    "list": []
+  }
+}
diff --git a/daemon/default-config.json b/daemon/default-config.json
new file mode 100644 (file)
index 0000000..302014a
--- /dev/null
@@ -0,0 +1,27 @@
+{
+    "Server":
+    {
+        "Address":"unix:///tmp/osui.sock",
+        "LogFile":"/var/log/opensnitchd.log"
+    },
+    "DefaultAction": "allow",
+    "DefaultDuration": "once",
+    "InterceptUnknown": false,
+    "ProcMonitorMethod": "ebpf",
+    "LogLevel": 2,
+    "LogUTC": true,
+    "LogMicro": false,
+    "Firewall": "nftables",
+    "Rules": {
+        "Path": "/etc/opensnitchd/rules/"
+    },
+    "Stats": {
+        "MaxEvents": 150,
+        "MaxStats": 25,
+        "Workers": 6
+    },
+    "Internal": {
+        "GCPercent": 100,
+        "FlushConnsOnStart": true
+    }
+}
diff --git a/daemon/dns/ebpfhook.go b/daemon/dns/ebpfhook.go
new file mode 100644 (file)
index 0000000..83a37df
--- /dev/null
@@ -0,0 +1,212 @@
+package dns
+
+import (
+       "bytes"
+       "debug/elf"
+       "encoding/binary"
+       "errors"
+       "fmt"
+       "net"
+       "os"
+       "os/signal"
+       "strings"
+       "syscall"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       bpf "github.com/iovisor/gobpf/elf"
+)
+
+/*
+#cgo LDFLAGS: -ldl
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <link.h>
+#include <dlfcn.h>
+#include <string.h>
+
+char* find_libc() {
+    void *handle;
+    struct link_map * map;
+
+    handle = dlopen(NULL, RTLD_NOW);
+    if (handle == NULL) {
+        fprintf(stderr, "EBPF-DNS dlopen() failed: %s\n", dlerror());
+        return NULL;
+    }
+
+
+    if (dlinfo(handle, RTLD_DI_LINKMAP, &map) == -1) {
+        fprintf(stderr, "EBPF-DNS: dlinfo failed: %s\n", dlerror());
+        return NULL;
+    }
+
+    while(1){
+        if(map == NULL){
+            break;
+        }
+
+        if(strstr(map->l_name, "libc.so")){
+            fprintf(stderr,"found %s\n", map->l_name);
+            return map->l_name;
+        }
+        map = map->l_next;
+    }
+    return NULL;
+}
+
+
+*/
+import "C"
+
+type nameLookupEvent struct {
+       AddrType uint32
+       IP       [16]uint8
+       Host     [252]byte
+}
+
+func findLibc() (string, error) {
+       ret := C.find_libc()
+
+       if ret == nil {
+               return "", errors.New("Could not find path to libc.so")
+       }
+       str := C.GoString(ret)
+
+       return str, nil
+}
+
+// Iterates over all symbols in an elf file and returns the offset matching the provided symbol name.
+func lookupSymbol(elffile *elf.File, symbolName string) (uint64, error) {
+       symbols, err := elffile.DynamicSymbols()
+       if err != nil {
+               return 0, err
+       }
+       for _, symb := range symbols {
+               if symb.Name == symbolName {
+                       return symb.Value, nil
+               }
+       }
+       return 0, fmt.Errorf("Symbol: '%s' not found", symbolName)
+}
+
+// ListenerEbpf starts listening for DNS events.
+func ListenerEbpf(ebpfModPath string) error {
+       m, err := core.LoadEbpfModule("opensnitch-dns.o", ebpfModPath)
+       if err != nil {
+               log.Error("[eBPF DNS]: %s", err)
+               return err
+       }
+       defer m.Close()
+
+       // libbcc resolves the offsets for us. without bcc the offset for uprobes must parsed from the elf files
+       // some how 0 must be replaced with the offset of getaddrinfo bcc does this using bcc_resolve_symname
+
+       // Attaching to uprobe using perf open might be a better aproach requires https://github.com/iovisor/gobpf/pull/277
+       libcFile, err := findLibc()
+
+       if err != nil {
+               log.Error("EBPF-DNS: Failed to find libc.so: %v", err)
+               return err
+       }
+
+       libcElf, err := elf.Open(libcFile)
+       if err != nil {
+               log.Error("EBPF-DNS: Failed to open %s: %v", libcFile, err)
+               return err
+       }
+       probesAttached := 0
+       for uprobe := range m.IterUprobes() {
+               probeFunction := strings.Replace(uprobe.Name, "uretprobe/", "", 1)
+               probeFunction = strings.Replace(probeFunction, "uprobe/", "", 1)
+               offset, err := lookupSymbol(libcElf, probeFunction)
+               if err != nil {
+                       log.Warning("EBPF-DNS: Failed to find symbol for uprobe %s (offset: %d): %s\n", uprobe.Name, offset, err)
+                       continue
+               }
+               err = bpf.AttachUprobe(uprobe, libcFile, offset)
+               if err != nil {
+                       log.Warning("EBPF-DNS: Failed to attach uprobe %s : %s, (%s, %d)\n", uprobe.Name, err, libcFile, offset)
+                       continue
+               }
+               probesAttached++
+       }
+
+       if probesAttached == 0 {
+               log.Warning("EBPF-DNS: Failed to find symbols for uprobes.")
+               return errors.New("Failed to find symbols for uprobes")
+       }
+
+       // Reading Events
+       channel := make(chan []byte)
+       //log.Warning("EBPF-DNS: %+v\n", m)
+       perfMap, err := bpf.InitPerfMap(m, "events", channel, nil)
+       if err != nil {
+               log.Error("EBPF-DNS: Failed to init perf map: %s\n", err)
+               return err
+       }
+       sig := make(chan os.Signal, 1)
+       exitChannel := make(chan bool)
+       signal.Notify(sig,
+               syscall.SIGHUP,
+               syscall.SIGINT,
+               syscall.SIGTERM,
+               syscall.SIGKILL,
+               syscall.SIGQUIT)
+
+       for i := 0; i < 5; i++ {
+               go spawnDNSWorker(i, channel, exitChannel)
+       }
+
+       perfMap.PollStart()
+       <-sig
+       log.Info("EBPF-DNS: Received signal: terminating ebpf dns hook.")
+       perfMap.PollStop()
+       for i := 0; i < 5; i++ {
+               exitChannel <- true
+       }
+       return nil
+}
+
+func spawnDNSWorker(id int, channel chan []byte, exitChannel chan bool) {
+
+       log.Debug("dns worker initialized #%d", id)
+       var event nameLookupEvent
+       for {
+               select {
+
+               case <-time.After(1 * time.Millisecond):
+                       continue
+               case <-exitChannel:
+                       goto Exit
+               default:
+                       data := <-channel
+                       if len(data) > 0 {
+                               log.Debug("(%d) EBPF-DNS: LookupEvent %d %x %x %x", id, len(data), data[:4], data[4:20], data[20:])
+                       }
+                       err := binary.Read(bytes.NewBuffer(data), binary.LittleEndian, &event)
+                       if err != nil {
+                               log.Warning("(%d) EBPF-DNS: Failed to decode ebpf nameLookupEvent: %s\n", id, err)
+                               continue
+                       }
+                       // Convert C string (null-terminated) to Go string
+                       host := string(event.Host[:bytes.IndexByte(event.Host[:], 0)])
+                       var ip net.IP
+                       // 2 -> AF_INET (ipv4)
+                       if event.AddrType == 2 {
+                               ip = net.IP(event.IP[:4])
+                       } else {
+                               ip = net.IP(event.IP[:])
+                       }
+
+                       log.Debug("(%d) EBPF-DNS: Tracking Resolved Message: %s -> %s\n", id, host, ip.String())
+                       Track(ip.String(), host)
+               }
+       }
+
+Exit:
+       log.Debug("DNS worker #%d closed", id)
+}
diff --git a/daemon/dns/parse.go b/daemon/dns/parse.go
new file mode 100644 (file)
index 0000000..971eafe
--- /dev/null
@@ -0,0 +1,21 @@
+package dns
+
+import (
+       "github.com/evilsocket/opensnitch/daemon/netfilter"
+       "github.com/google/gopacket/layers"
+)
+
+// GetQuestions retrieves the domain names a process is trying to resolve.
+func GetQuestions(nfp *netfilter.Packet) (questions []string) {
+       dnsLayer := nfp.Packet.Layer(layers.LayerTypeDNS)
+       if dnsLayer == nil {
+               return questions
+       }
+
+       dns, _ := dnsLayer.(*layers.DNS)
+       for _, dnsQuestion := range dns.Questions {
+               questions = append(questions, string(dnsQuestion.Name))
+       }
+
+       return questions
+}
diff --git a/daemon/dns/systemd/monitor.go b/daemon/dns/systemd/monitor.go
new file mode 100644 (file)
index 0000000..0b51e2b
--- /dev/null
@@ -0,0 +1,237 @@
+// Package systemd defines several utilities to interact with systemd.
+//
+// ResolvedMonitor:
+//  * To debug systemd-resolved queries and inspect the protocol:
+//    - resolvectl monitor
+//  * Resources:
+//   - https://github.com/systemd/systemd/blob/main/src/resolve/resolvectl.c
+//   - The protocol used to send and receive data is varlink:
+//     https://github.com/varlink/go
+//     https://github.com/systemd/systemd/blob/main/src/resolve/resolved-varlink.c
+//   - https://systemd.io/RESOLVED-VPNS/
+package systemd
+
+import (
+       "context"
+       "errors"
+       "fmt"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+
+       "github.com/varlink/go/varlink"
+)
+
+// whenever there's a new DNS response, this callback will be invoked.
+// the second parameter is a MonitorResponse struct that will be filled with
+// data.
+type resolvedCallback func(context.Context, interface{}) (uint64, error)
+
+const (
+       // SuccessState is the string returned by systemd-resolved when a DNS query is successful.
+       // Other states: https://github.com/systemd/systemd/blob/main/src/resolve/resolved-dns-transaction.c#L3608
+       SuccessState = "success"
+
+       socketPath              = "/run/systemd/resolve/io.systemd.Resolve.Monitor"
+       resolvedSubscribeMethod = "io.systemd.Resolve.Monitor.SubscribeQueryResults"
+
+       // DNSTypeA A
+       DNSTypeA = 1
+       // DNSTypeAAAA AAAA
+       DNSTypeAAAA = 28
+       // DNSTypeCNAME cname
+       DNSTypeCNAME = 5
+)
+
+// QuestionMonitorResponse represents a DNS query
+//  "question": [{"class": 1, "type": 28,"name": "images.site.com"}],
+type QuestionMonitorResponse struct {
+       Name  string `json:"name"`
+       Class int    `json:"class"`
+       Type  int    `json:"type"`
+}
+
+// KeyType holds question that generated the answer
+/*answer: [{
+       "rr": {
+               "key": {
+                       "class": 1,
+                       "type": 28,
+                       "name": "images.site.com"
+               },
+               "address": [100, 13, 45, 111]
+       },
+       "raw": "DFJFKE343443EFKEREKET=",
+       "ifindex": 3
+}]*/
+type KeyType struct {
+       Name  string `json:"name"`
+       Class int    `json:"class"`
+       Type  int    `json:"type"`
+}
+
+// RRType represents a DNS answer
+// if the response is a CNAME, Address will be nil, and Name a domain name.
+type RRType struct {
+       Key     QuestionMonitorResponse `json:"key"`
+       Address []byte                  `json:"address"`
+       Name    string                  `json:"name"`
+}
+
+// AnswerMonitorResponse represents the DNS answer of a DNS query.
+type AnswerMonitorResponse struct {
+       RR      RRType `json:"rr"`
+       Raw     string `json:"raw"`
+       Ifindex int    `json:"ifindex"`
+}
+
+// MonitorResponse represents the systemd-resolved protocol message
+// sent over the wire, that holds the answer to a DNS query.
+type MonitorResponse struct {
+       State    string                    `json:"state"`
+       Question []QuestionMonitorResponse `json:"question"`
+       // CollectedQuestions
+       // "collectedQuestions":[{"class":1,"type":1,"name":"translate.google.com"}]
+       Answer    []AnswerMonitorResponse `json:"answer"`
+       Continues bool                    `json:"continues"`
+}
+
+// ResolvedMonitor represents a systemd-resolved monitor
+type ResolvedMonitor struct {
+       mu     *sync.RWMutex
+       Ctx    context.Context
+       Cancel context.CancelFunc
+
+       // connection with the systemd-resolved unix socket:
+       // /run/systemd/resolve/io.systemd.Resolve.Monitor
+       Conn *varlink.Connection
+
+       // channel where all the DNS respones will be sent
+       ChanResponse chan *MonitorResponse
+
+       // error channel to signal any problem
+       ChanConnError chan error
+
+       // callback that is emited when systemd-resolved resolves a domain name.
+       receiverCb resolvedCallback
+
+       connected bool
+}
+
+// NewResolvedMonitor returns a new ResolvedMonitor object.
+// With this object you can passively read DNS answers.
+func NewResolvedMonitor() (*ResolvedMonitor, error) {
+       if core.Exists(socketPath) == false {
+               return nil, fmt.Errorf("%s doesn't exist", socketPath)
+       }
+       ctx, cancel := context.WithCancel(context.Background())
+
+       return &ResolvedMonitor{
+               mu:            &sync.RWMutex{},
+               Ctx:           ctx,
+               Cancel:        cancel,
+               ChanResponse:  make(chan *MonitorResponse),
+               ChanConnError: make(chan error),
+       }, nil
+}
+
+// Connect opens a unix socket with systemd-resolved
+func (r *ResolvedMonitor) Connect() (*varlink.Connection, error) {
+       r.mu.Lock()
+       defer r.mu.Unlock()
+
+       var err error
+       r.Conn, err = varlink.NewConnection(r.Ctx, fmt.Sprintf("unix://%s", socketPath))
+       if err != nil {
+               return nil, err
+       }
+
+       r.connected = true
+       go r.connPoller()
+       return r.Conn, nil
+}
+
+// if we're connected to the unix socket, check every few seconds if we're still
+// connected, and if not, reconnect, to survive to systemd-resolved restarts.
+func (r *ResolvedMonitor) connPoller() {
+       for {
+               select {
+               case <-time.After(5 * time.Second):
+                       if r.isConnected() {
+                               continue
+                       }
+                       log.Debug("ResolvedMonitor not connected")
+                       if _, err := r.Connect(); err == nil {
+                               r.Subscribe()
+                       }
+                       goto Exit
+               }
+       }
+Exit:
+       log.Debug("ResolvedMonitor connection poller exit.")
+}
+
+// Subscribe sends the instruction to systemd-resolved to start monitoring
+// DNS answers.
+func (r *ResolvedMonitor) Subscribe() error {
+       if r.isConnected() == false {
+               return errors.New("Not connected")
+       }
+       var err error
+       type emptyT struct{}
+       empty := &emptyT{}
+       r.receiverCb, err = r.Conn.Send(r.Ctx, resolvedSubscribeMethod, empty, varlink.Continues|varlink.More)
+       if err != nil {
+               return err
+       }
+       go r.monitor(r.Ctx, r.ChanResponse, r.ChanConnError, r.receiverCb)
+
+       return nil
+}
+
+// monitor will listen for DNS answers from systemd-resolved.
+func (r *ResolvedMonitor) monitor(ctx context.Context, chanResponse chan *MonitorResponse, chanConnError chan error, callback resolvedCallback) {
+       for {
+               m := &MonitorResponse{}
+               continues, err := callback(ctx, m)
+               if err != nil {
+                       chanConnError <- err
+                       goto Exit
+               }
+               if continues != varlink.Continues {
+                       goto Exit
+               }
+               log.Debug("ResolvedMonitor >> new response: %#v", m)
+               chanResponse <- m
+       }
+
+Exit:
+       r.mu.Lock()
+       r.connected = false
+       r.mu.Unlock()
+       log.Debug("ResolvedMonitor.monitor() exit.")
+}
+
+// GetDNSResponses returns a channel that you can use to read responses.
+func (r *ResolvedMonitor) GetDNSResponses() chan *MonitorResponse {
+       return r.ChanResponse
+}
+
+// Exit returns a channel to listen for connection errors.
+func (r *ResolvedMonitor) Exit() chan error {
+       return r.ChanConnError
+}
+
+// Close closes the unix socket with systemd-resolved
+func (r *ResolvedMonitor) Close() {
+       r.ChanConnError <- nil
+       r.Cancel()
+}
+
+func (r *ResolvedMonitor) isConnected() bool {
+       r.mu.RLock()
+       defer r.mu.RUnlock()
+       return r.connected
+}
diff --git a/daemon/dns/track.go b/daemon/dns/track.go
new file mode 100644 (file)
index 0000000..bf3b723
--- /dev/null
@@ -0,0 +1,102 @@
+package dns
+
+import (
+       "net"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+
+       "github.com/google/gopacket"
+       "github.com/google/gopacket/layers"
+)
+
+var (
+       responses = make(map[string]string, 0)
+       lock      = sync.RWMutex{}
+)
+
+// TrackAnswers obtains the resolved domains of a DNS query.
+// If the packet is UDP DNS, the domain names are added to the list of resolved domains.
+func TrackAnswers(packet gopacket.Packet) bool {
+       udpLayer := packet.Layer(layers.LayerTypeUDP)
+       if udpLayer == nil {
+               return false
+       }
+
+       udp, ok := udpLayer.(*layers.UDP)
+       if ok == false || udp == nil {
+               return false
+       }
+       if udp.SrcPort != 53 {
+               return false
+       }
+
+       dnsLayer := packet.Layer(layers.LayerTypeDNS)
+       if dnsLayer == nil {
+               return false
+       }
+
+       dnsAns, ok := dnsLayer.(*layers.DNS)
+       if ok == false || dnsAns == nil {
+               return false
+       }
+
+       for _, ans := range dnsAns.Answers {
+               if ans.Name != nil {
+                       if ans.IP != nil {
+                               Track(ans.IP.String(), string(ans.Name))
+                       } else if ans.CNAME != nil {
+                               Track(string(ans.CNAME), string(ans.Name))
+                       }
+               }
+       }
+
+       return true
+}
+
+// Track adds a resolved domain to the list.
+func Track(resolved string, hostname string) {
+       lock.Lock()
+       defer lock.Unlock()
+
+       if len(resolved) > 3 && resolved[0:4] == "127." {
+               return
+       }
+       if resolved == "::1" || resolved == hostname {
+               return
+       }
+       responses[resolved] = hostname
+
+       log.Debug("New DNS record: %s -> %s", resolved, hostname)
+}
+
+// Host returns if a resolved domain is in the list.
+func Host(resolved string) (host string, found bool) {
+       lock.RLock()
+       defer lock.RUnlock()
+
+       host, found = responses[resolved]
+       return
+}
+
+// HostOr checks if an IP has a domain name already resolved.
+// If the domain is in the list it's returned, otherwise the IP will be returned.
+func HostOr(ip net.IP, or string) string {
+       if host, found := Host(ip.String()); found == true {
+               // host might have been CNAME; go back until we reach the "root"
+               seen := make(map[string]bool) // prevent possibility of loops
+               for {
+                       orig, had := Host(host)
+                       if seen[orig] {
+                               break
+                       }
+                       if !had {
+                               break
+                       }
+                       seen[orig] = true
+                       host = orig
+               }
+               return host
+       }
+       return or
+}
diff --git a/daemon/firewall/common/common.go b/daemon/firewall/common/common.go
new file mode 100644 (file)
index 0000000..67e3aff
--- /dev/null
@@ -0,0 +1,170 @@
+package common
+
+import (
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+// default arguments for various functions
+var (
+       EnableRule     = true
+       DoLogErrors    = true
+       ForcedDelRules = true
+       ReloadRules    = true
+       RestoreChains  = true
+       BackupChains   = true
+       ReloadConf     = true
+)
+
+type (
+       callback     func()
+       callbackBool func() bool
+
+       // Common holds common fields and functionality of both firewalls,
+       // iptables and nftables.
+       Common struct {
+               RulesChecker *time.Ticker
+               ErrChan      chan string
+               QueueNum     uint16
+               stopChecker  chan bool
+               Running      bool
+               Intercepting bool
+               FwEnabled    bool
+               sync.RWMutex
+       }
+)
+
+// ErrorsChan returns the channel where the errors are sent to.
+func (c *Common) ErrorsChan() <-chan string {
+       return c.ErrChan
+}
+
+// ErrChanEmpty checks if the errors channel is empty.
+func (c *Common) ErrChanEmpty() bool {
+       return len(c.ErrChan) == 0
+}
+
+// SendError sends an error to the channel of errors.
+func (c *Common) SendError(err string) {
+       log.Warning("%s", err)
+
+       if len(c.ErrChan) >= cap(c.ErrChan) {
+               log.Debug("fw errors channel full, emptying errChan")
+               for e := range c.ErrChan {
+                       log.Warning("%s", e)
+                       if c.ErrChanEmpty() {
+                               break
+                       }
+               }
+               return
+       }
+       select {
+       case c.ErrChan <- err:
+       case <-time.After(100 * time.Millisecond):
+               log.Warning("SendError() channel locked? REVIEW")
+       }
+}
+
+// SetQueueNum sets the queue number used by the firewall.
+// It's the queue where all intercepted connections will be sent.
+func (c *Common) SetQueueNum(qNum *int) {
+       c.Lock()
+       defer c.Unlock()
+
+       if qNum != nil {
+               c.QueueNum = uint16(*qNum)
+       }
+
+}
+
+// IsRunning returns if the firewall is running or not.
+func (c *Common) IsRunning() bool {
+       c.RLock()
+       defer c.RUnlock()
+
+       return c != nil && c.Running
+}
+
+// IsFirewallEnabled returns if the firewall is running or not.
+func (c *Common) IsFirewallEnabled() bool {
+       c.RLock()
+       defer c.RUnlock()
+
+       return c != nil && c.FwEnabled
+}
+
+// IsIntercepting returns if the firewall is running or not.
+func (c *Common) IsIntercepting() bool {
+       c.RLock()
+       defer c.RUnlock()
+
+       return c != nil && c.Intercepting
+}
+
+// NewRulesChecker starts monitoring interception rules.
+// We expect to have 2 rules loaded: one to intercept DNS responses and another one
+// to intercept network traffic.
+func (c *Common) NewRulesChecker(areRulesLoaded callbackBool, reloadRules callback) {
+       c.Lock()
+       defer c.Unlock()
+
+       if c.RulesChecker != nil {
+               c.RulesChecker.Stop()
+               select {
+               case c.stopChecker <- true:
+               case <-time.After(5 * time.Millisecond):
+                       log.Error("NewRulesChecker: timed out stopping monitor rules")
+               }
+       }
+       c.stopChecker = make(chan bool, 1)
+       c.RulesChecker = time.NewTicker(time.Second * 10)
+
+       go startCheckingRules(c.stopChecker, c.RulesChecker, areRulesLoaded, reloadRules)
+}
+
+// StartCheckingRules monitors if our rules are loaded.
+// If the rules to intercept traffic are not loaded, we'll try to insert them again.
+func startCheckingRules(exitChan <-chan bool, rulesChecker *time.Ticker, areRulesLoaded callbackBool, reloadRules callback) {
+       for {
+               select {
+               case <-exitChan:
+                       goto Exit
+               case _, active := <-rulesChecker.C:
+                       if !active {
+                               goto Exit
+                       }
+
+                       if areRulesLoaded() == false {
+                               reloadRules()
+                       }
+               }
+       }
+
+Exit:
+       log.Info("exit checking firewall rules")
+}
+
+// StopCheckingRules stops checking if firewall rules are loaded.
+func (c *Common) StopCheckingRules() {
+       c.Lock()
+       defer c.Unlock()
+
+       if c.RulesChecker != nil {
+               select {
+               case c.stopChecker <- true:
+                       close(c.stopChecker)
+               case <-time.After(5 * time.Millisecond):
+                       // We should not arrive here
+                       log.Error("StopCheckingRules: timed out stopping monitor rules")
+               }
+
+               c.RulesChecker.Stop()
+               c.RulesChecker = nil
+       }
+}
+
+func (c *Common) reloadCallback(callback func()) {
+       callback()
+}
diff --git a/daemon/firewall/config/config.go b/daemon/firewall/config/config.go
new file mode 100644 (file)
index 0000000..43c6c9e
--- /dev/null
@@ -0,0 +1,254 @@
+// Package config provides functionality to load and monitor the system
+// firewall rules.
+// It's inherited by the different firewall packages (iptables, nftables).
+//
+// The firewall rules defined by the user are reloaded in these cases:
+// - When the file system-fw.json changes.
+// - When the firewall rules are not present when listing them.
+package config
+
+import (
+       "encoding/json"
+       "io/ioutil"
+       "os"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/fsnotify/fsnotify"
+)
+
+// ExprValues holds the statements' options:
+// "Name": "ct",
+// "Values": [
+// {
+//   "Key":   "state",
+//   "Value": "established"
+// },
+// {
+//   "Key":   "state",
+//   "Value": "related"
+// }]
+type ExprValues struct {
+       Key   string
+       Value string
+}
+
+// ExprStatement holds the definition of matches to use against connections.
+//{
+//     "Op": "!=",
+//     "Name": "tcp",
+//     "Values": [
+//             {
+//                     "Key": "dport",
+//                     "Value": "443"
+//             }
+//     ]
+//}
+type ExprStatement struct {
+       Op     string        // ==, !=, ... Only one per expression set.
+       Name   string        // tcp, udp, ct, daddr, log, ...
+       Values []*ExprValues // dport 8000
+}
+
+// Expressions holds the array of expressions that create the rules
+type Expressions struct {
+       Statement *ExprStatement
+}
+
+// FwRule holds the fields of a rule
+type FwRule struct {
+       *sync.RWMutex
+       // we need to keep old fields in the struct. Otherwise when receiving a conf from the GUI, the legacy rules would be deleted.
+       Chain            string // TODO: deprecated, remove
+       Table            string // TODO: deprecated, remove
+       Parameters       string // TODO: deprecated, remove
+       UUID             string
+       Description      string
+       Target           string
+       TargetParameters string
+       Expressions      []*Expressions
+       Position         uint64 `json:",string"`
+       Enabled          bool
+}
+
+// FwChain holds the information that defines a firewall chain.
+// It also contains the firewall table definition that it belongs to.
+type FwChain struct {
+       // table fields
+       Table  string
+       Family string
+       // chain fields
+       Name        string
+       Description string
+       Priority    string
+       Type        string
+       Hook        string
+       Policy      string
+       Rules       []*FwRule
+}
+
+// IsInvalid checks if the chain has been correctly configured.
+func (fc *FwChain) IsInvalid() bool {
+       return fc.Name == "" || fc.Family == "" || fc.Table == ""
+}
+
+type rulesList struct {
+       Rule *FwRule
+}
+
+type chainsList struct {
+       Rule   *FwRule // TODO: deprecated, remove
+       Chains []*FwChain
+}
+
+// SystemConfig holds the list of rules to be added to the system
+type SystemConfig struct {
+       SystemRules []*chainsList
+       sync.RWMutex
+       Version uint32
+       Enabled bool
+}
+
+// Config holds the functionality to re/load the firewall configuration from disk.
+// This is the configuration to manage the system firewall (iptables, nftables).
+type Config struct {
+       watcher         *fsnotify.Watcher
+       monitorExitChan chan bool
+       // preload will be called after daemon startup, whilst reload when a modification is performed.
+       preloadCallback func()
+       // reloadCallback is called after the configuration is written.
+       reloadCallback func()
+       file           string
+       SysConfig      SystemConfig
+
+       sync.Mutex
+}
+
+// NewSystemFwConfig initializes config fields
+func (c *Config) NewSystemFwConfig(preLoadCb, reLoadCb func()) (*Config, error) {
+       var err error
+       watcher, err := fsnotify.NewWatcher()
+       if err != nil {
+               log.Warning("Error creating firewall config watcher: %s", err)
+               return nil, err
+       }
+
+       c.Lock()
+       defer c.Unlock()
+
+       c.file = "/etc/opensnitchd/system-fw.json"
+       c.monitorExitChan = make(chan bool, 1)
+       c.preloadCallback = preLoadCb
+       c.reloadCallback = reLoadCb
+       c.watcher = watcher
+       return c, nil
+}
+
+func (c *Config) SetFile(file string) {
+       c.file = file
+}
+
+// LoadDiskConfiguration reads and loads the firewall configuration from disk
+func (c *Config) LoadDiskConfiguration(reload bool) error {
+       c.Lock()
+       defer c.Unlock()
+
+       raw, err := ioutil.ReadFile(c.file)
+       if err != nil {
+               log.Error("Error reading firewall configuration from disk %s: %s", c.file, err)
+               return err
+       }
+
+       if err = c.loadConfiguration(raw); err != nil {
+               return err
+       }
+       // we need to monitor the configuration file for changes, regardless if it's
+       // malformed or not.
+       c.watcher.Remove(c.file)
+       if err := c.watcher.Add(c.file); err != nil {
+               log.Error("Could not watch firewall configuration: %s", err)
+               return err
+       }
+
+       if reload {
+               c.reloadCallback()
+               return nil
+       }
+
+       go c.monitorConfigWorker()
+
+       return nil
+}
+
+// loadConfigutation reads the system firewall rules from disk.
+// Then the rules are added based on the configuration defined.
+func (c *Config) loadConfiguration(rawConfig []byte) error {
+       c.SysConfig.Lock()
+       defer c.SysConfig.Unlock()
+
+       // delete old system rules, that may be different from the new ones
+       c.preloadCallback()
+
+       if err := json.Unmarshal(rawConfig, &c.SysConfig); err != nil {
+               // we only log the parser error, giving the user a chance to write a valid config
+               log.Error("Error parsing firewall configuration %s: %s", c.file, err)
+               return err
+       }
+       log.Info("fw configuration loaded")
+
+       return nil
+}
+
+// SaveConfiguration saves configuration to disk.
+// This event dispatches a reload of the configuration.
+func (c *Config) SaveConfiguration(rawConfig string) error {
+       conf, err := json.MarshalIndent([]byte(rawConfig), "  ", "  ")
+       if err != nil {
+               log.Error("saving json firewall configuration: %s %s", err, conf)
+               return err
+       }
+
+       if err = os.Chmod(c.file, 0600); err != nil {
+               log.Warning("unable to set system-fw.json permissions: %s", err)
+       }
+       if err = ioutil.WriteFile(c.file, []byte(rawConfig), 0600); err != nil {
+               log.Error("writing firewall configuration to disk: %s", err)
+               return err
+       }
+       return nil
+}
+
+// StopConfigWatcher stops the configuration watcher and stops the subroutine.
+func (c *Config) StopConfigWatcher() {
+       c.Lock()
+       defer c.Unlock()
+
+       if c.monitorExitChan != nil {
+               c.monitorExitChan <- true
+               close(c.monitorExitChan)
+       }
+
+       if c.watcher != nil {
+               c.watcher.Remove(c.file)
+               c.watcher.Close()
+       }
+}
+
+func (c *Config) monitorConfigWorker() {
+       for {
+               select {
+               case <-c.monitorExitChan:
+                       goto Exit
+               case event := <-c.watcher.Events:
+                       if (event.Op&fsnotify.Write == fsnotify.Write) || (event.Op&fsnotify.Remove == fsnotify.Remove) {
+                               c.LoadDiskConfiguration(common.ReloadConf)
+                       }
+               }
+       }
+Exit:
+       log.Debug("stop monitoring firewall config file")
+       c.Lock()
+       c.monitorExitChan = nil
+       c.Unlock()
+}
diff --git a/daemon/firewall/iptables/iptables.go b/daemon/firewall/iptables/iptables.go
new file mode 100644 (file)
index 0000000..513ecd7
--- /dev/null
@@ -0,0 +1,198 @@
+package iptables
+
+import (
+       "bytes"
+       "encoding/json"
+       "os/exec"
+       "regexp"
+       "strings"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+       "github.com/golang/protobuf/jsonpb"
+)
+
+// Action is the modifier we apply to a rule.
+type Action string
+
+const (
+       // Name is the name that identifies this firewall
+       Name = "iptables"
+       // SystemRulePrefix prefix added to each system rule
+       SystemRulePrefix = "opensnitch-filter"
+)
+
+// Actions we apply to the firewall.
+const (
+       ADD      = Action("-A")
+       INSERT   = Action("-I")
+       DELETE   = Action("-D")
+       FLUSH    = Action("-F")
+       NEWCHAIN = Action("-N")
+       DELCHAIN = Action("-X")
+       POLICY   = Action("-P")
+
+       DROP   = Action("DROP")
+       ACCEPT = Action("ACCEPT")
+)
+
+// SystemRule blabla
+type SystemRule struct {
+       Rule  *config.FwRule
+       Table string
+       Chain string
+}
+
+// SystemChains keeps track of the fw rules that have been added to the system.
+type SystemChains struct {
+       Rules map[string]*SystemRule
+       sync.RWMutex
+}
+
+// Iptables struct holds the fields of the iptables fw
+type Iptables struct {
+       regexRulesQuery       *regexp.Regexp
+       regexSystemRulesQuery *regexp.Regexp
+       bin                   string
+       bin6                  string
+       chains                SystemChains
+       common.Common
+       config.Config
+       sync.Mutex
+}
+
+// Fw initializes a new Iptables object
+func Fw() (*Iptables, error) {
+       if err := IsAvailable(); err != nil {
+               return nil, err
+       }
+
+       reRulesQuery, _ := regexp.Compile(`NFQUEUE.*ctstate NEW,RELATED.*NFQUEUE num.*bypass`)
+       reSystemRulesQuery, _ := regexp.Compile(SystemRulePrefix + ".*")
+
+       ipt := &Iptables{
+               bin:                   "iptables",
+               bin6:                  "ip6tables",
+               regexRulesQuery:       reRulesQuery,
+               regexSystemRulesQuery: reSystemRulesQuery,
+               chains: SystemChains{
+                       Rules: make(map[string]*SystemRule),
+               },
+       }
+       return ipt, nil
+}
+
+// Name returns the firewall name
+func (ipt *Iptables) Name() string {
+       return Name
+}
+
+// Init inserts the firewall rules and starts monitoring for firewall
+// changes.
+func (ipt *Iptables) Init(qNum *int) {
+       if ipt.IsRunning() {
+               return
+       }
+       ipt.SetQueueNum(qNum)
+       ipt.ErrChan = make(chan string, 100)
+
+       // In order to clean up any existing firewall rule before start,
+       // we need to load the fw configuration first to know what rules
+       // were configured.
+       ipt.NewSystemFwConfig(ipt.preloadConfCallback, ipt.reloadRulesCallback)
+       ipt.LoadDiskConfiguration(!common.ReloadConf)
+
+       // start from a clean state
+       ipt.CleanRules(false)
+       ipt.EnableInterception()
+       ipt.AddSystemRules(!common.ReloadRules, common.BackupChains)
+
+       ipt.Running = true
+}
+
+// Stop deletes the firewall rules, allowing network traffic.
+func (ipt *Iptables) Stop() {
+       if ipt.Running == false {
+               return
+       }
+       ipt.StopConfigWatcher()
+       ipt.StopCheckingRules()
+       ipt.CleanRules(log.GetLogLevel() == log.DEBUG)
+
+       ipt.Running = false
+}
+
+// IsAvailable checks if iptables is installed in the system.
+// If it's not, we'll default to nftables.
+func IsAvailable() error {
+       _, err := exec.Command("iptables", []string{"-V"}...).CombinedOutput()
+       if err != nil {
+               return err
+       }
+       return nil
+}
+
+// EnableInterception adds fw rules to intercept connections.
+func (ipt *Iptables) EnableInterception() {
+       if err4, err6 := ipt.QueueConnections(common.EnableRule, true); err4 != nil || err6 != nil {
+               log.Fatal("Error while running conntrack firewall rule: %s %s", err4, err6)
+       } else if err4, err6 = ipt.QueueDNSResponses(common.EnableRule, true); err4 != nil || err6 != nil {
+               log.Error("Error while running DNS firewall rule: %s %s", err4, err6)
+       }
+       // start monitoring firewall rules to intercept network traffic
+       ipt.NewRulesChecker(ipt.AreRulesLoaded, ipt.reloadRulesCallback)
+}
+
+// DisableInterception removes firewall rules to intercept outbound connections.
+func (ipt *Iptables) DisableInterception(logErrors bool) {
+       ipt.StopCheckingRules()
+       ipt.QueueDNSResponses(!common.EnableRule, logErrors)
+       ipt.QueueConnections(!common.EnableRule, logErrors)
+}
+
+// CleanRules deletes the rules we added.
+func (ipt *Iptables) CleanRules(logErrors bool) {
+       ipt.DisableInterception(logErrors)
+       ipt.DeleteSystemRules(common.ForcedDelRules, common.BackupChains, logErrors)
+}
+
+// Serialize converts the configuration from json to protobuf
+func (ipt *Iptables) Serialize() (*protocol.SysFirewall, error) {
+       sysfw := &protocol.SysFirewall{}
+       jun := jsonpb.Unmarshaler{
+               AllowUnknownFields: true,
+       }
+       rawConfig, err := json.Marshal(&ipt.SysConfig)
+       if err != nil {
+               log.Error("nfables.Serialize() struct to string error: %s", err)
+               return nil, err
+       }
+       // string to proto
+       if err := jun.Unmarshal(strings.NewReader(string(rawConfig)), sysfw); err != nil {
+               log.Error("nfables.Serialize() string to protobuf error: %s", err)
+               return nil, err
+       }
+
+       return sysfw, nil
+}
+
+// Deserialize converts a protocolbuffer structure to json.
+func (ipt *Iptables) Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
+       jun := jsonpb.Marshaler{
+               OrigName:     true,
+               EmitDefaults: false,
+               Indent:       "  ",
+       }
+
+       var b bytes.Buffer
+       if err := jun.Marshal(&b, sysfw); err != nil {
+               log.Error("nfables.Deserialize() error 2: %s", err)
+               return nil, err
+       }
+       return b.Bytes(), nil
+
+       //return nil, fmt.Errorf("iptables.Deserialize() not implemented")
+}
diff --git a/daemon/firewall/iptables/monitor.go b/daemon/firewall/iptables/monitor.go
new file mode 100644 (file)
index 0000000..fbcec2f
--- /dev/null
@@ -0,0 +1,69 @@
+package iptables
+
+import (
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+// AreRulesLoaded checks if the firewall rules for intercept traffic are loaded.
+func (ipt *Iptables) AreRulesLoaded() bool {
+       var outMangle6 string
+
+       outMangle, err := core.Exec("iptables", []string{"-n", "-L", "OUTPUT", "-t", "mangle"})
+       if err != nil {
+               return false
+       }
+
+       if core.IPv6Enabled {
+               outMangle6, err = core.Exec("ip6tables", []string{"-n", "-L", "OUTPUT", "-t", "mangle"})
+               if err != nil {
+                       return false
+               }
+       }
+
+       systemRulesLoaded := true
+       ipt.chains.RLock()
+       if len(ipt.chains.Rules) > 0 {
+               for _, rule := range ipt.chains.Rules {
+                       if chainOut4, err4 := core.Exec("iptables", []string{"-n", "-L", rule.Chain, "-t", rule.Table}); err4 == nil {
+                               if ipt.regexSystemRulesQuery.FindString(chainOut4) == "" {
+                                       systemRulesLoaded = false
+                                       break
+                               }
+                       }
+                       if core.IPv6Enabled {
+                               if chainOut6, err6 := core.Exec("ip6tables", []string{"-n", "-L", rule.Chain, "-t", rule.Table}); err6 == nil {
+                                       if ipt.regexSystemRulesQuery.FindString(chainOut6) == "" {
+                                               systemRulesLoaded = false
+                                               break
+                                       }
+                               }
+                       }
+               }
+       }
+       ipt.chains.RUnlock()
+
+       result := ipt.regexRulesQuery.FindString(outMangle) != "" &&
+               systemRulesLoaded
+
+       if core.IPv6Enabled {
+               result = result && ipt.regexRulesQuery.FindString(outMangle6) != ""
+       }
+
+       return result
+}
+
+// reloadRulesCallback gets called when the interception rules are not present or after the configuration file changes.
+func (ipt *Iptables) reloadRulesCallback() {
+       log.Important("firewall rules changed, reloading")
+       ipt.CleanRules(false)
+       ipt.AddSystemRules(common.ReloadRules, common.BackupChains)
+       ipt.EnableInterception()
+}
+
+// preloadConfCallback gets called before the fw configuration is reloaded
+func (ipt *Iptables) preloadConfCallback() {
+       log.Info("iptables config changed, reloading")
+       ipt.DeleteSystemRules(common.ForcedDelRules, common.BackupChains, log.GetLogLevel() == log.DEBUG)
+}
diff --git a/daemon/firewall/iptables/rules.go b/daemon/firewall/iptables/rules.go
new file mode 100644 (file)
index 0000000..6eed842
--- /dev/null
@@ -0,0 +1,77 @@
+package iptables
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/vishvananda/netlink"
+)
+
+// RunRule inserts or deletes a firewall rule.
+func (ipt *Iptables) RunRule(action Action, enable bool, logError bool, rule []string) (err4, err6 error) {
+       if enable == false {
+               action = "-D"
+       }
+
+       rule = append([]string{string(action)}, rule...)
+
+       ipt.Lock()
+       defer ipt.Unlock()
+
+       if _, err4 = core.Exec(ipt.bin, rule); err4 != nil {
+               if logError {
+                       log.Error("Error while running firewall rule, ipv4 err: %s", err4)
+                       log.Error("rule: %s", rule)
+               }
+       }
+
+       // On some systems IPv6 is disabled
+       if core.IPv6Enabled {
+               if _, err6 = core.Exec(ipt.bin6, rule); err6 != nil {
+                       if logError {
+                               log.Error("Error while running firewall rule, ipv6 err: %s", err6)
+                               log.Error("rule: %s", rule)
+                       }
+               }
+       }
+
+       return
+}
+
+// QueueDNSResponses redirects DNS responses to us, in order to keep a cache
+// of resolved domains.
+// INPUT --protocol udp --sport 53 -j NFQUEUE --queue-num 0 --queue-bypass
+func (ipt *Iptables) QueueDNSResponses(enable bool, logError bool) (err4, err6 error) {
+       return ipt.RunRule(INSERT, enable, logError, []string{
+               "INPUT",
+               "--protocol", "udp",
+               "--sport", "53",
+               "-j", "NFQUEUE",
+               "--queue-num", fmt.Sprintf("%d", ipt.QueueNum),
+               "--queue-bypass",
+       })
+}
+
+// QueueConnections inserts the firewall rule which redirects connections to us.
+// Connections are queued until the user denies/accept them, or reaches a timeout.
+// OUTPUT -t mangle -m conntrack --ctstate NEW,RELATED -j NFQUEUE --queue-num 0 --queue-bypass
+func (ipt *Iptables) QueueConnections(enable bool, logError bool) (error, error) {
+       err4, err6 := ipt.RunRule(ADD, enable, logError, []string{
+               "OUTPUT",
+               "-t", "mangle",
+               "-m", "conntrack",
+               "--ctstate", "NEW,RELATED",
+               "-j", "NFQUEUE",
+               "--queue-num", fmt.Sprintf("%d", ipt.QueueNum),
+               "--queue-bypass",
+       })
+       if enable {
+               // flush conntrack as soon as netfilter rule is set. This ensures that already-established
+               // connections will go to netfilter queue.
+               if err := netlink.ConntrackTableFlush(netlink.ConntrackTable); err != nil {
+                       log.Error("error in ConntrackTableFlush %s", err)
+               }
+       }
+       return err4, err6
+}
diff --git a/daemon/firewall/iptables/system.go b/daemon/firewall/iptables/system.go
new file mode 100644 (file)
index 0000000..9626787
--- /dev/null
@@ -0,0 +1,155 @@
+package iptables
+
+import (
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+)
+
+// CreateSystemRule creates the custom firewall chains and adds them to the system.
+func (ipt *Iptables) CreateSystemRule(rule *config.FwRule, table, chain, hook string, logErrors bool) bool {
+       ipt.chains.Lock()
+       defer ipt.chains.Unlock()
+       if rule == nil {
+               return false
+       }
+       if table == "" {
+               table = "filter"
+       }
+       if hook == "" {
+               hook = rule.Chain
+       }
+
+       chainName := SystemRulePrefix + "-" + hook
+       if _, ok := ipt.chains.Rules[table+"-"+chainName]; ok {
+               return false
+       }
+       ipt.RunRule(NEWCHAIN, common.EnableRule, logErrors, []string{chainName, "-t", table})
+
+       // Insert the rule at the top of the chain
+       if err4, err6 := ipt.RunRule(INSERT, common.EnableRule, logErrors, []string{hook, "-t", table, "-j", chainName}); err4 == nil && err6 == nil {
+               ipt.chains.Rules[table+"-"+chainName] = &SystemRule{
+                       Table: table,
+                       Chain: chain,
+                       Rule:  rule,
+               }
+       }
+       return true
+
+}
+
+// AddSystemRules creates the system firewall from configuration.
+func (ipt *Iptables) AddSystemRules(reload, backupExistingChains bool) {
+       // Version 0 has no Enabled field, so it'd be always false
+       if ipt.SysConfig.Enabled == false && ipt.SysConfig.Version > 0 {
+               return
+       }
+
+       for _, cfg := range ipt.SysConfig.SystemRules {
+               if cfg.Rule != nil {
+                       ipt.CreateSystemRule(cfg.Rule, cfg.Rule.Table, cfg.Rule.Chain, cfg.Rule.Chain, common.EnableRule)
+                       ipt.AddSystemRule(ADD, cfg.Rule, cfg.Rule.Table, cfg.Rule.Chain, common.EnableRule)
+                       continue
+               }
+
+               if cfg.Chains != nil {
+                       for _, chn := range cfg.Chains {
+                               if chn.Hook != "" && chn.Type != "" {
+                                       ipt.ConfigureChainPolicy(chn.Type, chn.Hook, chn.Policy, true)
+                               }
+                       }
+               }
+       }
+}
+
+// DeleteSystemRules deletes the system rules.
+// If force is false and the rule has not been previously added,
+// it won't try to delete the rules. Otherwise it'll try to delete them.
+func (ipt *Iptables) DeleteSystemRules(force, backupExistingChains, logErrors bool) {
+       ipt.chains.Lock()
+       defer ipt.chains.Unlock()
+
+       for _, fwCfg := range ipt.SysConfig.SystemRules {
+               if fwCfg.Rule == nil {
+                       continue
+               }
+               chain := SystemRulePrefix + "-" + fwCfg.Rule.Chain
+               if _, ok := ipt.chains.Rules[fwCfg.Rule.Table+"-"+chain]; !ok && !force {
+                       continue
+               }
+               ipt.RunRule(FLUSH, common.EnableRule, false, []string{chain, "-t", fwCfg.Rule.Table})
+               ipt.RunRule(DELETE, !common.EnableRule, logErrors, []string{fwCfg.Rule.Chain, "-t", fwCfg.Rule.Table, "-j", chain})
+               ipt.RunRule(DELCHAIN, common.EnableRule, false, []string{chain, "-t", fwCfg.Rule.Table})
+               delete(ipt.chains.Rules, fwCfg.Rule.Table+"-"+chain)
+
+               for _, chn := range fwCfg.Chains {
+                       if chn.Table == "" {
+                               chn.Table = "filter"
+                       }
+                       chain := SystemRulePrefix + "-" + chn.Hook
+                       if _, ok := ipt.chains.Rules[chn.Type+"-"+chain]; !ok && !force {
+                               continue
+                       }
+
+                       ipt.RunRule(FLUSH, common.EnableRule, logErrors, []string{chain, "-t", chn.Type})
+                       ipt.RunRule(DELETE, !common.EnableRule, logErrors, []string{chn.Hook, "-t", chn.Type, "-j", chain})
+                       ipt.RunRule(DELCHAIN, common.EnableRule, logErrors, []string{chain, "-t", chn.Type})
+                       delete(ipt.chains.Rules, chn.Type+"-"+chain)
+
+               }
+       }
+}
+
+// DeleteSystemRule deletes a new rule.
+func (ipt *Iptables) DeleteSystemRule(action Action, rule *config.FwRule, table, chain string, enable bool) (err4, err6 error) {
+       chainName := SystemRulePrefix + "-" + chain
+       if table == "" {
+               table = "filter"
+       }
+       r := []string{chainName, "-t", table}
+       if rule.Parameters != "" {
+               r = append(r, strings.Split(rule.Parameters, " ")...)
+       }
+       r = append(r, []string{"-j", rule.Target}...)
+       if rule.TargetParameters != "" {
+               r = append(r, strings.Split(rule.TargetParameters, " ")...)
+       }
+
+       return ipt.RunRule(action, enable, true, r)
+}
+
+// AddSystemRule inserts a new rule.
+func (ipt *Iptables) AddSystemRule(action Action, rule *config.FwRule, table, chain string, enable bool) (err4, err6 error) {
+       if rule == nil {
+               return nil, nil
+       }
+       ipt.RLock()
+       defer ipt.RUnlock()
+
+       chainName := SystemRulePrefix + "-" + chain
+       if table == "" {
+               table = "filter"
+       }
+       r := []string{chainName, "-t", table}
+       if rule.Parameters != "" {
+               r = append(r, strings.Split(rule.Parameters, " ")...)
+       }
+       r = append(r, []string{"-j", rule.Target}...)
+       if rule.TargetParameters != "" {
+               r = append(r, strings.Split(rule.TargetParameters, " ")...)
+       }
+
+       return ipt.RunRule(ADD, enable, true, r)
+}
+
+// ConfigureChainPolicy configures chains policy.
+func (ipt *Iptables) ConfigureChainPolicy(table, hook, policy string, logError bool) {
+       // TODO: list all policies before modify them, and restore the original state on exit.
+       // still, if we exit abruptly, we might left the system badly configured.
+       ipt.RunRule(POLICY, true, logError, []string{
+               hook,
+               strings.ToUpper(policy),
+               "-t", table,
+       })
+}
diff --git a/daemon/firewall/nftables/chains.go b/daemon/firewall/nftables/chains.go
new file mode 100644 (file)
index 0000000..e4a7921
--- /dev/null
@@ -0,0 +1,188 @@
+package nftables
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+)
+
+// getChainKey returns the identifier that will be used to link chains and rules.
+// When adding a new chain the key is stored, then later when adding a rule we get
+// the chain that the rule belongs to by this key.
+func getChainKey(name string, table *nftables.Table) string {
+       if table == nil {
+               return ""
+       }
+       return fmt.Sprintf("%s-%s-%d", name, table.Name, table.Family)
+}
+
+// GetChain gets an existing chain
+func GetChain(name string, table *nftables.Table) *nftables.Chain {
+       key := getChainKey(name, table)
+       if ch, ok := sysChains.Load(key); ok {
+               return ch.(*nftables.Chain)
+       }
+       return nil
+}
+
+// AddChain adds a new chain to nftables.
+// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
+func (n *Nft) AddChain(name, table, family string, priority *nftables.ChainPriority, ctype nftables.ChainType, hook *nftables.ChainHook, policy nftables.ChainPolicy) *nftables.Chain {
+       if family == "" {
+               family = exprs.NFT_FAMILY_INET
+       }
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               log.Error("%s addChain, Error getting table: %s, %s", logTag, table, family)
+               return nil
+       }
+
+       var chain *nftables.Chain
+       // Verify if the chain already exists, and reuse it if it does.
+       // In some systems it fails adding a chain when it already exists, whilst in others
+       // it doesn't.
+       key := getChainKey(name, tbl)
+       chain = n.GetChain(name, tbl, family)
+       if chain != nil {
+               if _, exists := sysChains.Load(key); exists {
+                       sysChains.Delete(key)
+               }
+               chain.Policy = &policy
+               n.Conn.AddChain(chain)
+       } else {
+               // nft list chains
+               chain = n.Conn.AddChain(&nftables.Chain{
+                       Name:     strings.ToLower(name),
+                       Table:    tbl,
+                       Type:     ctype,
+                       Hooknum:  hook,
+                       Priority: priority,
+                       Policy:   &policy,
+               })
+               if chain == nil {
+                       log.Debug("%s AddChain() chain == nil", logTag)
+                       return nil
+               }
+       }
+
+       sysChains.Store(key, chain)
+       return chain
+}
+
+// GetChain checks if a chain in the given table exists.
+func (n *Nft) GetChain(name string, table *nftables.Table, family string) *nftables.Chain {
+       if chains, err := n.Conn.ListChains(); err == nil {
+               for _, c := range chains {
+                       if name == c.Name && table.Name == c.Table.Name && GetFamilyCode(family) == c.Table.Family {
+                               return c
+                       }
+               }
+       }
+       return nil
+}
+
+// regular chains are user-defined chains, to better organize fw rules.
+// https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Adding_regular_chains
+func (n *Nft) addRegularChain(name, table, family string) error {
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               return fmt.Errorf("%s addRegularChain, Error getting table: %s, %s", logTag, table, family)
+       }
+
+       chain := n.Conn.AddChain(&nftables.Chain{
+               Name:  name,
+               Table: tbl,
+       })
+       if chain == nil {
+               return fmt.Errorf("%s error adding regular chain: %s", logTag, name)
+       }
+       key := getChainKey(name, tbl)
+       sysChains.Store(key, chain)
+
+       return nil
+}
+
+// AddInterceptionChains adds the needed chains to intercept traffic.
+func (n *Nft) AddInterceptionChains() error {
+       var filterPolicy nftables.ChainPolicy
+       var manglePolicy nftables.ChainPolicy
+       filterPolicy = nftables.ChainPolicyAccept
+       manglePolicy = nftables.ChainPolicyAccept
+
+       tbl := n.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+       if tbl != nil {
+               key := getChainKey(exprs.NFT_HOOK_INPUT, tbl)
+               ch, found := sysChains.Load(key)
+               if key != "" && found {
+                       filterPolicy = *ch.(*nftables.Chain).Policy
+               }
+       }
+       tbl = n.GetTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
+       if tbl != nil {
+               key := getChainKey(exprs.NFT_HOOK_OUTPUT, tbl)
+               ch, found := sysChains.Load(key)
+               if key != "" && found {
+                       manglePolicy = *ch.(*nftables.Chain).Policy
+               }
+       }
+
+       // nft list tables
+       n.AddChain(exprs.NFT_HOOK_INPUT, exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityFilter, nftables.ChainTypeFilter, nftables.ChainHookInput, filterPolicy)
+       if !n.Commit() {
+               return fmt.Errorf("Error adding DNS interception chain input-filter-inet")
+       }
+       n.AddChain(exprs.NFT_HOOK_OUTPUT, exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityMangle, nftables.ChainTypeRoute, nftables.ChainHookOutput, manglePolicy)
+       if !n.Commit() {
+               log.Error("(1) Error adding interception chain mangle-output-inet, trying with type Filter instead of Route")
+
+               // Workaround for kernels 4.x and maybe others.
+               // @see firewall/nftables/utils.go:GetChainPriority()
+               chainPrio, chainType := GetChainPriority(exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_OUTPUT)
+               n.AddChain(exprs.NFT_HOOK_OUTPUT, exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET,
+                       chainPrio, chainType, nftables.ChainHookOutput, manglePolicy)
+               if !n.Commit() {
+                       return fmt.Errorf("(2) Error adding interception chain mangle-output-inet with type Filter. Report it on github please, specifying the distro and the kernel")
+               }
+       }
+
+       return nil
+}
+
+// DelChain deletes a chain from the system.
+func (n *Nft) DelChain(chain *nftables.Chain) error {
+       n.Conn.DelChain(chain)
+       sysChains.Delete(getChainKey(chain.Name, chain.Table))
+       if !n.Commit() {
+               return fmt.Errorf("[nftables] error deleting chain %s, %s", chain.Name, chain.Table.Name)
+       }
+
+       return nil
+}
+
+// backupExistingChains saves chains with Accept policy.
+// If the user configures the chain policy to Drop, we need to set it back to Accept,
+// in order not to block incoming connections.
+func (n *Nft) backupExistingChains() {
+       if chains, err := n.Conn.ListChains(); err == nil {
+               for _, c := range chains {
+                       if c.Policy != nil && *c.Policy == nftables.ChainPolicyAccept {
+                               log.Debug("%s backing up existing chain with policy ACCEPT: %s, %s", logTag, c.Name, c.Table.Name)
+                               origSysChains[getChainKey(c.Name, c.Table)] = c
+                       }
+               }
+       }
+}
+
+func (n *Nft) restoreBackupChains() {
+       for _, c := range origSysChains {
+               log.Debug("%s Restoring chain policy to accept: %s, %s", logTag, c.Name, c.Table.Name)
+               *c.Policy = nftables.ChainPolicyAccept
+               n.Conn.AddChain(c)
+       }
+       n.Commit()
+}
diff --git a/daemon/firewall/nftables/chains_test.go b/daemon/firewall/nftables/chains_test.go
new file mode 100644 (file)
index 0000000..31e25b5
--- /dev/null
@@ -0,0 +1,85 @@
+package nftables_test
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+)
+
+func TestChains(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       if nftest.Fw.AddInterceptionTables() != nil {
+               t.Error("Error adding interception tables")
+       }
+
+       t.Run("AddChain", func(t *testing.T) {
+               filterPolicy := nftables.ChainPolicyAccept
+               chn := nftest.Fw.AddChain(
+                       exprs.NFT_HOOK_INPUT,
+                       exprs.NFT_CHAIN_FILTER,
+                       exprs.NFT_FAMILY_INET,
+                       nftables.ChainPriorityFilter,
+                       nftables.ChainTypeFilter,
+                       nftables.ChainHookInput,
+                       filterPolicy)
+               if chn == nil {
+                       t.Error("chain input-filter-inet not created")
+               }
+               if !nftest.Fw.Commit() {
+                       t.Error("error adding input-filter-inet chain")
+               }
+       })
+
+       t.Run("getChain", func(t *testing.T) {
+               tblfilter := nftest.Fw.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+               if tblfilter == nil {
+                       t.Error("table filter-inet not created")
+               }
+
+               chn := nftest.Fw.GetChain(exprs.NFT_HOOK_INPUT, tblfilter, exprs.NFT_FAMILY_INET)
+               if chn == nil {
+                       t.Error("chain input-filter-inet not added")
+               }
+       })
+
+       t.Run("delChain", func(t *testing.T) {
+               tblfilter := nftest.Fw.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+               if tblfilter == nil {
+                       t.Error("table filter-inet not created")
+               }
+
+               chn := nftest.Fw.GetChain(exprs.NFT_HOOK_INPUT, tblfilter, exprs.NFT_FAMILY_INET)
+               if chn == nil {
+                       t.Error("chain input-filter-inet not added")
+               }
+
+               if err := nftest.Fw.DelChain(chn); err != nil {
+                       t.Error("error deleting chain input-filter-inet")
+               }
+       })
+
+       nftest.Fw.DelSystemTables()
+}
+
+// TestAddInterceptionChains checks if the needed tables and chains have been created.
+// We use 2: output-mangle-inet for intercepting outbound connections, and input-filter-inet for DNS responses interception
+func TestAddInterceptionChains(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       if err := nftest.Fw.AddInterceptionTables(); err != nil {
+               t.Errorf("Error adding interception tables: %s", err)
+       }
+
+       if err := nftest.Fw.AddInterceptionChains(); err != nil {
+               t.Errorf("Error adding interception chains: %s", err)
+       }
+
+       nftest.Fw.DelSystemTables()
+}
diff --git a/daemon/firewall/nftables/exprs/counter.go b/daemon/firewall/nftables/exprs/counter.go
new file mode 100644 (file)
index 0000000..1bc0a52
--- /dev/null
@@ -0,0 +1,15 @@
+package exprs
+
+import (
+       "github.com/google/nftables/expr"
+)
+
+// NewExprCounter returns a counter for packets or bytes.
+func NewExprCounter(counterName string) *[]expr.Any {
+       return &[]expr.Any{
+               &expr.Objref{
+                       Type: 1,
+                       Name: counterName,
+               },
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/counter_test.go b/daemon/firewall/nftables/exprs/counter_test.go
new file mode 100644 (file)
index 0000000..4fcf137
--- /dev/null
@@ -0,0 +1,53 @@
+package exprs_test
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+)
+
+func TestExprNamedCounter(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       // we must create the table before the counter object.
+       tbl, _ := nftest.Fw.AddTable("yyy", exprs.NFT_FAMILY_INET)
+
+       nftest.Fw.Conn.AddObj(
+               &nftables.CounterObj{
+                       Table: &nftables.Table{
+                               Name:   "yyy",
+                               Family: nftables.TableFamilyINet,
+                       },
+                       Name:    "xxx-counter",
+                       Bytes:   0,
+                       Packets: 0,
+               },
+       )
+
+       r, _ := nftest.AddTestRule(t, conn, exprs.NewExprCounter("xxx-counter"))
+       if r == nil {
+               t.Error("Error adding counter rule")
+               return
+       }
+
+       objs, err := nftest.Fw.Conn.GetObjects(tbl)
+       if err != nil {
+               t.Errorf("Error retrieving objects from table %s: %s", tbl.Name, err)
+       }
+       if len(objs) != 1 {
+               t.Errorf("%d objects found, expected 1", len(objs))
+       }
+       counter, ok := objs[0].(*nftables.CounterObj)
+       if !ok {
+               t.Errorf("returned Obj is not CounterObj: %+v", objs[0])
+       }
+       if counter.Name != "xxx-counter" {
+               t.Errorf("CounterObj name differs: %s, expected 'xxx-counter'", counter.Name)
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/ct.go b/daemon/firewall/nftables/exprs/ct.go
new file mode 100644 (file)
index 0000000..4254429
--- /dev/null
@@ -0,0 +1,121 @@
+package exprs
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+)
+
+// Example https://github.com/google/nftables/blob/master/nftables_test.go#L1234
+// https://wiki.nftables.org/wiki-nftables/index.php/Setting_packet_metainformation
+
+// NewExprCtMark returns a new ct expression.
+// # set
+// # nft --debug netlink  add rule filter output mark set 1
+// ip filter output
+//  [ immediate reg 1 0x00000001 ]
+//  [ meta set mark with reg 1 ]
+//
+// match mark:
+// nft --debug netlink add rule mangle prerouting ct mark 123
+// [ ct load mark => reg 1 ]
+// [ cmp eq reg 1 0x0000007b ]
+func NewExprCtMark(setMark bool, value string, cmpOp *expr.CmpOp) (*[]expr.Any, error) {
+       mark, err := strconv.Atoi(value)
+       if err != nil {
+               return nil, fmt.Errorf("Invalid conntrack mark: %s (%s)", err, value)
+       }
+
+       exprCtMark := []expr.Any{}
+       exprCtMark = append(exprCtMark, []expr.Any{
+               &expr.Immediate{
+                       Register: 1,
+                       Data:     binaryutil.NativeEndian.PutUint32(uint32(mark)),
+               },
+               &expr.Ct{
+                       Key:            expr.CtKeyMARK,
+                       Register:       1,
+                       SourceRegister: setMark,
+               },
+       }...)
+       if setMark == false {
+               exprCtMark = append(exprCtMark, []expr.Any{
+                       &expr.Cmp{Op: *cmpOp, Register: 1, Data: binaryutil.NativeEndian.PutUint32(uint32(mark))},
+               }...)
+       }
+
+       return &exprCtMark, nil
+}
+
+// NewExprCtState returns a new ct expression.
+func NewExprCtState(ctFlags []*config.ExprValues) (*[]expr.Any, error) {
+       mask := uint32(0)
+
+       for _, flag := range ctFlags {
+               found, msk, err := parseInlineCtStates(flag.Value)
+               if err != nil {
+                       return nil, err
+               }
+               if found {
+                       mask |= msk
+                       continue
+               }
+
+               msk, err = getCtState(flag.Value)
+               if err != nil {
+                       return nil, err
+               }
+               mask |= msk
+       }
+
+       return &[]expr.Any{
+               &expr.Ct{
+                       Register: 1, SourceRegister: false, Key: expr.CtKeySTATE,
+               },
+               &expr.Bitwise{
+                       SourceRegister: 1,
+                       DestRegister:   1,
+                       Len:            4,
+                       Mask:           binaryutil.NativeEndian.PutUint32(mask),
+                       Xor:            binaryutil.NativeEndian.PutUint32(0),
+               },
+       }, nil
+}
+
+func parseInlineCtStates(flags string) (found bool, mask uint32, err error) {
+       // a "state" flag may be compounded of multiple values, separated by commas:
+       // related,established
+       fgs := strings.Split(flags, ",")
+       if len(fgs) > 0 {
+               for _, fg := range fgs {
+                       msk, err := getCtState(fg)
+                       if err != nil {
+                               return false, 0, err
+                       }
+                       mask |= msk
+                       found = true
+               }
+       }
+       return
+}
+
+func getCtState(flag string) (mask uint32, err error) {
+       switch strings.ToLower(flag) {
+       case CT_STATE_NEW:
+               mask |= expr.CtStateBitNEW
+       case CT_STATE_ESTABLISHED:
+               mask |= expr.CtStateBitESTABLISHED
+       case CT_STATE_RELATED:
+               mask |= expr.CtStateBitRELATED
+       case CT_STATE_INVALID:
+               mask |= expr.CtStateBitINVALID
+       default:
+               return 0, fmt.Errorf("Invalid conntrack flag: %s", flag)
+       }
+
+       return
+}
diff --git a/daemon/firewall/nftables/exprs/ct_test.go b/daemon/firewall/nftables/exprs/ct_test.go
new file mode 100644 (file)
index 0000000..08a52c4
--- /dev/null
@@ -0,0 +1,224 @@
+package exprs_test
+
+import (
+       "fmt"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+)
+
+func TestExprCtMark(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       type ctTestsT struct {
+               nftest.TestsT
+               setMark bool
+       }
+
+       cmp := expr.CmpOpEq
+       tests := []ctTestsT{
+               {
+                       TestsT: nftest.TestsT{
+                               Name:             "test-ct-set-mark-666",
+                               Parms:            "666",
+                               ExpectedExprsNum: 2,
+                               ExpectedExprs: []interface{}{
+                                       &expr.Immediate{
+                                               Register: 1,
+                                               Data:     binaryutil.NativeEndian.PutUint32(uint32(666)),
+                                       },
+                                       &expr.Ct{
+                                               Key:            expr.CtKeyMARK,
+                                               Register:       1,
+                                               SourceRegister: true,
+                                       },
+                               },
+                       },
+                       setMark: true,
+               },
+               {
+                       TestsT: nftest.TestsT{
+                               Name:             "test-ct-check-mark-666",
+                               Parms:            "666",
+                               ExpectedExprsNum: 3,
+                               ExpectedExprs: []interface{}{
+                                       &expr.Immediate{
+                                               Register: 1,
+                                               Data:     binaryutil.NativeEndian.PutUint32(uint32(666)),
+                                       },
+                                       &expr.Ct{
+                                               Key:            expr.CtKeyMARK,
+                                               Register:       1,
+                                               SourceRegister: false,
+                                       },
+                                       &expr.Cmp{
+                                               Op:       cmp,
+                                               Register: 1,
+                                               Data:     binaryutil.NativeEndian.PutUint32(uint32(666)),
+                                       },
+                               },
+                       },
+                       setMark: false,
+               },
+               {
+                       TestsT: nftest.TestsT{
+                               Name:             "test-invalid-ct-check-mark",
+                               Parms:            "0x29a",
+                               ExpectedExprsNum: 3,
+                               ExpectedExprs:    []interface{}{},
+                               ExpectedFail:     true,
+                       },
+                       setMark: false,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+                       quotaExpr, err := exprs.NewExprCtMark(test.setMark, test.TestsT.Parms, &cmp)
+                       if err != nil && !test.ExpectedFail {
+                               t.Errorf("Error creating expr Ct: %s", quotaExpr)
+                               return
+                       } else if err != nil && test.ExpectedFail {
+                               return
+                       }
+
+                       r, _ := nftest.AddTestRule(t, conn, quotaExpr)
+                       if r == nil && !test.ExpectedFail {
+                               t.Error("Error adding rule with Ct expression")
+                       }
+
+                       if !nftest.AreExprsValid(t, &test.TestsT, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+               })
+       }
+
+}
+
+func TestExprCtState(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       type ctTestsT struct {
+               nftest.TestsT
+               setMark bool
+       }
+
+       tests := []nftest.TestsT{
+               {
+                       Name:  "test-ct-single-state",
+                       Parms: "",
+                       Values: []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_CT_STATE,
+                                       Value: exprs.CT_STATE_NEW,
+                               },
+                       },
+                       ExpectedExprsNum: 2,
+                       ExpectedExprs: []interface{}{
+                               &expr.Ct{
+                                       Register: 1, SourceRegister: false, Key: expr.CtKeySTATE,
+                               },
+                               &expr.Bitwise{
+                                       SourceRegister: 1,
+                                       DestRegister:   1,
+                                       Len:            4,
+                                       Mask:           binaryutil.NativeEndian.PutUint32(expr.CtStateBitNEW),
+                                       Xor:            binaryutil.NativeEndian.PutUint32(0),
+                               },
+                       },
+                       ExpectedFail: false,
+               },
+               {
+                       Name:  "test-ct-multiple-states",
+                       Parms: "",
+                       Values: []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_CT_STATE,
+                                       Value: fmt.Sprint(exprs.CT_STATE_NEW, ",", exprs.CT_STATE_ESTABLISHED),
+                               },
+                       },
+                       ExpectedExprsNum: 2,
+                       ExpectedExprs: []interface{}{
+                               &expr.Ct{
+                                       Register: 1, SourceRegister: false, Key: expr.CtKeySTATE,
+                               },
+                               &expr.Bitwise{
+                                       SourceRegister: 1,
+                                       DestRegister:   1,
+                                       Len:            4,
+                                       Mask:           binaryutil.NativeEndian.PutUint32(expr.CtStateBitNEW | expr.CtStateBitESTABLISHED),
+                                       Xor:            binaryutil.NativeEndian.PutUint32(0),
+                               },
+                       },
+                       ExpectedFail: false,
+               },
+               {
+                       Name:  "test-invalid-ct-state",
+                       Parms: "",
+                       Values: []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_CT_STATE,
+                                       Value: "xxx",
+                               },
+                       },
+                       ExpectedExprsNum: 2,
+                       ExpectedExprs:    []interface{}{},
+                       ExpectedFail:     true,
+               },
+               {
+                       Name:  "test-invalid-ct-states",
+                       Parms: "",
+                       Values: []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_CT_STATE,
+                                       Value: "new,xxx",
+                               },
+                       },
+                       ExpectedExprsNum: 2,
+                       ExpectedExprs:    []interface{}{},
+                       ExpectedFail:     true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+                       quotaExpr, err := exprs.NewExprCtState(test.Values)
+                       if err != nil && !test.ExpectedFail {
+                               t.Errorf("Error creating expr Ct: %s", quotaExpr)
+                               return
+                       } else if err != nil && test.ExpectedFail {
+                               return
+                       }
+
+                       r, _ := nftest.AddTestRule(t, conn, quotaExpr)
+                       if r == nil && !test.ExpectedFail {
+                               t.Error("Error adding rule with Quota expression")
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+               })
+       }
+
+}
diff --git a/daemon/firewall/nftables/exprs/enums.go b/daemon/firewall/nftables/exprs/enums.go
new file mode 100644 (file)
index 0000000..fbfce09
--- /dev/null
@@ -0,0 +1,192 @@
+package exprs
+
+// keywords used in the configuration to define rules.
+const (
+       // https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
+       NFT_CHAIN_MANGLE    = "mangle"
+       NFT_CHAIN_FILTER    = "filter"
+       NFT_CHAIN_RAW       = "raw"
+       NFT_CHAIN_SECURITY  = "security"
+       NFT_CHAIN_NATDEST   = "natdest"
+       NFT_CHAIN_NATSOURCE = "natsource"
+       NFT_CHAIN_CONNTRACK = "conntrack"
+       NFT_CHAIN_SELINUX   = "selinux"
+
+       NFT_HOOK_INPUT       = "input"
+       NFT_HOOK_OUTPUT      = "output"
+       NFT_HOOK_PREROUTING  = "prerouting"
+       NFT_HOOK_POSTROUTING = "postrouting"
+       NFT_HOOK_INGRESS     = "ingress"
+       NFT_HOOK_EGRESS      = "egress"
+       NFT_HOOK_FORWARD     = "forward"
+
+       NFT_TABLE_INET = "inet"
+       NFT_TABLE_NAT  = "nat"
+       // TODO
+       NFT_TABLE_ARP    = "arp"
+       NFT_TABLE_BRIDGE = "bridge"
+       NFT_TABLE_NETDEV = "netdev"
+
+       NFT_FAMILY_IP     = "ip"
+       NFT_FAMILY_IP6    = "ip6"
+       NFT_FAMILY_INET   = "inet"
+       NFT_FAMILY_BRIDGE = "bridge"
+       NFT_FAMILY_ARP    = "arp"
+       NFT_FAMILY_NETDEV = "netdev"
+
+       VERDICT_ACCEPT = "accept"
+       VERDICT_DROP   = "drop"
+       VERDICT_REJECT = "reject"
+       VERDICT_RETURN = "return"
+       VERDICT_QUEUE  = "queue"
+
+       VERDICT_JUMP = "jump"
+       // TODO
+       VERDICT_GOTO       = "goto"
+       VERDICT_STOP       = "stop"
+       VERDICT_STOLEN     = "stolen"
+       VERDICT_CONTINUE   = "continue"
+       VERDICT_MASQUERADE = "masquerade"
+       VERDICT_DNAT       = "dnat"
+       VERDICT_SNAT       = "snat"
+       VERDICT_REDIRECT   = "redirect"
+       VERDICT_TPROXY     = "tproxy"
+
+       NFT_PARM_TO = "to"
+
+       NFT_QUEUE_NUM     = "num"
+       NFT_QUEUE_BY_PASS = "queue-bypass"
+
+       NFT_MASQ_RANDOM       = "random"
+       NFT_MASQ_FULLY_RANDOM = "fully-random"
+       NFT_MASQ_PERSISTENT   = "persistent"
+
+       NFT_PROTOCOL  = "protocol"
+       NFT_SPORT     = "sport"
+       NFT_DPORT     = "dport"
+       NFT_SADDR     = "saddr"
+       NFT_DADDR     = "daddr"
+       NFT_ICMP_CODE = "code"
+       NFT_ICMP_TYPE = "type"
+
+       NFT_ETHER = "ether"
+
+       NFT_IIFNAME = "iifname"
+       NFT_OIFNAME = "oifname"
+
+       NFT_LOG        = "log"
+       NFT_LOG_PREFIX = "prefix"
+       // TODO
+       NFT_LOG_LEVEL        = "level"
+       NFT_LOG_LEVEL_EMERG  = "emerg"
+       NFT_LOG_LEVEL_ALERT  = "alert"
+       NFT_LOG_LEVEL_CRIT   = "crit"
+       NFT_LOG_LEVEL_ERR    = "err"
+       NFT_LOG_LEVEL_WARN   = "warn"
+       NFT_LOG_LEVEL_NOTICE = "notice"
+       NFT_LOG_LEVEL_INFO   = "info"
+       NFT_LOG_LEVEL_DEBUG  = "debug"
+       NFT_LOG_LEVEL_AUDIT  = "audit"
+       NFT_LOG_FLAGS        = "flags"
+
+       NFT_CT               = "ct"
+       NFT_CT_STATE         = "state"
+       NFT_CT_SET_MARK      = "set"
+       NFT_CT_MARK          = "mark"
+       CT_STATE_NEW         = "new"
+       CT_STATE_ESTABLISHED = "established"
+       CT_STATE_RELATED     = "related"
+       CT_STATE_INVALID     = "invalid"
+
+       NFT_NOTRACK = "notrack"
+
+       NFT_QUOTA            = "quota"
+       NFT_QUOTA_UNTIL      = "until"
+       NFT_QUOTA_OVER       = "over"
+       NFT_QUOTA_USED       = "used"
+       NFT_QUOTA_UNIT_BYTES = "bytes"
+       NFT_QUOTA_UNIT_KB    = "kbytes"
+       NFT_QUOTA_UNIT_MB    = "mbytes"
+       NFT_QUOTA_UNIT_GB    = "gbytes"
+
+       NFT_COUNTER         = "counter"
+       NFT_COUNTER_NAME    = "name"
+       NFT_COUNTER_PACKETS = "packets"
+       NFT_COUNTER_BYTES   = "bytes"
+
+       NFT_LIMIT             = "limit"
+       NFT_LIMIT_OVER        = "over"
+       NFT_LIMIT_BURST       = "burst"
+       NFT_LIMIT_UNITS_RATE  = "rate-units"
+       NFT_LIMIT_UNITS_TIME  = "time-units"
+       NFT_LIMIT_UNITS       = "units"
+       NFT_LIMIT_UNIT_SECOND = "second"
+       NFT_LIMIT_UNIT_MINUTE = "minute"
+       NFT_LIMIT_UNIT_HOUR   = "hour"
+       NFT_LIMIT_UNIT_DAY    = "day"
+       NFT_LIMIT_UNIT_KBYTES = "kbytes"
+       NFT_LIMIT_UNIT_MBYTES = "mbytes"
+
+       NFT_META          = "meta"
+       NFT_META_MARK     = "mark"
+       NFT_META_SET_MARK = "set"
+       NFT_META_PRIORITY = "priority"
+       NFT_META_NFTRACE  = "nftrace"
+       NFT_META_SET      = "set"
+       NFT_META_SKUID    = "skuid"
+       NFT_META_SKGID    = "skgid"
+       NFT_META_L4PROTO  = "l4proto"
+       NFT_META_PROTOCOL = "protocol"
+
+       NFT_PROTO_UDP      = "udp"
+       NFT_PROTO_UDPLITE  = "udplite"
+       NFT_PROTO_TCP      = "tcp"
+       NFT_PROTO_SCTP     = "sctp"
+       NFT_PROTO_DCCP     = "dccp"
+       NFT_PROTO_ICMP     = "icmp"
+       NFT_PROTO_ICMPX    = "icmpx"
+       NFT_PROTO_ICMPv6   = "icmpv6"
+       NFT_PROTO_AH       = "ah"
+       NFT_PROTO_ETHERNET = "ethernet"
+       NFT_PROTO_GRE      = "gre"
+       NFT_PROTO_IP       = "ip"
+       NFT_PROTO_IPIP     = "ipip"
+       NFT_PROTO_L2TP     = "l2tp"
+       NFT_PROTO_COMP     = "comp"
+       NFT_PROTO_IGMP     = "igmp"
+       NFT_PROTO_ESP      = "esp"
+       NFT_PROTO_RAW      = "raw"
+       NFT_PROTO_ENCAP    = "encap"
+
+       ICMP_NO_ROUTE           = "no-route"
+       ICMP_PROT_UNREACHABLE   = "prot-unreachable"
+       ICMP_PORT_UNREACHABLE   = "port-unreachable"
+       ICMP_NET_UNREACHABLE    = "net-unreachable"
+       ICMP_ADDR_UNREACHABLE   = "addr-unreachable"
+       ICMP_HOST_UNREACHABLE   = "host-unreachable"
+       ICMP_NET_PROHIBITED     = "net-prohibited"
+       ICMP_HOST_PROHIBITED    = "host-prohibited"
+       ICMP_ADMIN_PROHIBITED   = "admin-prohibited"
+       ICMP_REJECT_ROUTE       = "reject-route"
+       ICMP_REJECT_POLICY_FAIL = "policy-fail"
+
+       ICMP_ECHO_REPLY           = "echo-reply"
+       ICMP_ECHO_REQUEST         = "echo-request"
+       ICMP_SOURCE_QUENCH        = "source-quench"
+       ICMP_DEST_UNREACHABLE     = "destination-unreachable"
+       ICMP_REDIRECT             = "redirect"
+       ICMP_TIME_EXCEEDED        = "time-exceeded"
+       ICMP_INFO_REQUEST         = "info-request"
+       ICMP_INFO_REPLY           = "info-reply"
+       ICMP_PARAMETER_PROBLEM    = "parameter-problem"
+       ICMP_TIMESTAMP_REQUEST    = "timestamp-request"
+       ICMP_TIMESTAMP_REPLY      = "timestamp-reply"
+       ICMP_ROUTER_ADVERTISEMENT = "router-advertisement"
+       ICMP_ROUTER_SOLICITATION  = "router-solicitation"
+       ICMP_ADDRESS_MASK_REQUEST = "address-mask-request"
+       ICMP_ADDRESS_MASK_REPLY   = "address-mask-reply"
+
+       ICMP_PACKET_TOO_BIG          = "packet-too-big"
+       ICMP_NEIGHBOUR_SOLICITATION  = "neighbour-solicitation"
+       ICMP_NEIGHBOUR_ADVERTISEMENT = "neighbour-advertisement"
+)
diff --git a/daemon/firewall/nftables/exprs/ether.go b/daemon/firewall/nftables/exprs/ether.go
new file mode 100644 (file)
index 0000000..dfad247
--- /dev/null
@@ -0,0 +1,64 @@
+package exprs
+
+import (
+       "encoding/hex"
+       "fmt"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables/expr"
+)
+
+// NewExprEther creates a new expression to match ethernet MAC addresses
+func NewExprEther(values []*config.ExprValues) (*[]expr.Any, error) {
+       etherExpr := []expr.Any{}
+       macDir := uint32(6)
+
+       for _, eth := range values {
+               if eth.Key == NFT_DADDR {
+                       macDir = uint32(0)
+               } else {
+                       macDir = uint32(6)
+               }
+               macaddr, err := parseMACAddr(eth.Value)
+               if err != nil {
+                       return nil, err
+               }
+               etherExpr = append(etherExpr, []expr.Any{
+                       &expr.Meta{Key: expr.MetaKeyIIFTYPE, Register: 1},
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{0x01, 0x00},
+                       },
+                       &expr.Payload{
+                               DestRegister: 1,
+                               Base:         expr.PayloadBaseLLHeader,
+                               Offset:       macDir,
+                               Len:          6,
+                       },
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     macaddr,
+                       },
+               }...)
+       }
+       return &etherExpr, nil
+}
+
+func parseMACAddr(macValue string) ([]byte, error) {
+       mac := strings.Split(macValue, ":")
+       macaddr := make([]byte, 0)
+       if len(mac) != 6 {
+               return nil, fmt.Errorf("Invalid MAC address: %s", macValue)
+       }
+       for i, m := range mac {
+               mm, err := hex.DecodeString(m)
+               if err != nil {
+                       return nil, fmt.Errorf("Invalid MAC byte: %c (%s)", mm[i], macValue)
+               }
+               macaddr = append(macaddr, mm[0])
+       }
+       return macaddr, nil
+}
diff --git a/daemon/firewall/nftables/exprs/ether_test.go b/daemon/firewall/nftables/exprs/ether_test.go
new file mode 100644 (file)
index 0000000..998e1a9
--- /dev/null
@@ -0,0 +1,105 @@
+package exprs_test
+
+import (
+       "bytes"
+       "reflect"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/expr"
+)
+
+func TestExprEther(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       values := []*config.ExprValues{
+               &config.ExprValues{
+                       Key:   "ether",
+                       Value: "de:ad:be:af:ca:fe",
+               },
+       }
+
+       etherExpr, err := exprs.NewExprEther(values)
+       if err != nil {
+               t.Errorf("Error creating Ether expression: %s, %+v", err, values)
+       }
+
+       r, _ := nftest.AddTestRule(t, conn, etherExpr)
+       if r == nil {
+               t.Error("Error adding Ether rule")
+               return
+       }
+       if len(r.Exprs) != 4 {
+               t.Errorf("invalid rule created, we expected 4 expressions, got: %d", len(r.Exprs))
+       }
+
+       /*
+               expr Meta
+               expr Cmp
+               expr Payload
+               expr Cmp
+       */
+
+       t.Run("test-ether-expr meta", func(t *testing.T) {
+               e := r.Exprs[0] // meta
+               if reflect.TypeOf(e).String() != "*expr.Meta" {
+                       t.Errorf("first expression should be *expr.Meta, instead of: %s", reflect.TypeOf(e))
+               }
+               lMeta, ok := e.(*expr.Meta)
+               if !ok {
+                       t.Errorf("invalid meta expr: %T", e)
+               }
+               if lMeta.Key != expr.MetaKeyIIFTYPE {
+                       t.Errorf("invalid meta Key: %d, instead of %d", lMeta.Key, expr.MetaKeyIIFTYPE)
+               }
+       })
+
+       t.Run("test-ether-expr cmp", func(t *testing.T) {
+               e := r.Exprs[1] // cmp
+               if reflect.TypeOf(e).String() != "*expr.Cmp" {
+                       t.Errorf("second expression should be *expr.Cmp, instead of: %s", reflect.TypeOf(e))
+               }
+               lCmp, ok := e.(*expr.Cmp)
+               if !ok {
+                       t.Errorf("invalid cmp expr: %T", e)
+               }
+               if !bytes.Equal(lCmp.Data, []byte{0x01, 0x00}) {
+                       t.Errorf("invalid cmp data: %v", lCmp.Data)
+               }
+       })
+
+       t.Run("test-ether-expr payload", func(t *testing.T) {
+               e := r.Exprs[2] // payload
+               if reflect.TypeOf(e).String() != "*expr.Payload" {
+                       t.Errorf("third expression should be *expr.Payload, instead of: %s", reflect.TypeOf(e))
+               }
+               lPayload, ok := e.(*expr.Payload)
+               if !ok {
+                       t.Errorf("invalid payload expr: %T", e)
+               }
+               if lPayload.Base != expr.PayloadBaseLLHeader || lPayload.Offset != 6 || lPayload.Len != 6 {
+                       t.Errorf("invalid payload data: %v", lPayload)
+               }
+       })
+
+       t.Run("test-ether-expr cmp", func(t *testing.T) {
+               e := r.Exprs[3] // cmp
+               if reflect.TypeOf(e).String() != "*expr.Cmp" {
+                       t.Errorf("fourth expression should be *expr.Cmp, instead of: %s", reflect.TypeOf(e))
+               }
+               lCmp, ok := e.(*expr.Cmp)
+               if !ok {
+                       t.Errorf("invalid cmp expr: %T", e)
+               }
+               if !bytes.Equal(lCmp.Data, []byte{222, 173, 190, 175, 202, 254}) {
+                       t.Errorf("invalid cmp data: %q", lCmp.Data)
+               }
+       })
+
+}
diff --git a/daemon/firewall/nftables/exprs/iface.go b/daemon/firewall/nftables/exprs/iface.go
new file mode 100644 (file)
index 0000000..66d2637
--- /dev/null
@@ -0,0 +1,33 @@
+package exprs
+
+import (
+       "github.com/google/nftables/expr"
+)
+
+// NewExprIface returns a new network interface expression
+func NewExprIface(iface string, isOut bool, cmpOp expr.CmpOp) *[]expr.Any {
+       keyDev := expr.MetaKeyIIFNAME
+       if isOut {
+               keyDev = expr.MetaKeyOIFNAME
+       }
+       return &[]expr.Any{
+               &expr.Meta{Key: keyDev, Register: 1},
+               &expr.Cmp{
+                       Op:       cmpOp,
+                       Register: 1,
+                       Data:     ifname(iface),
+               },
+       }
+}
+
+// https://github.com/google/nftables/blob/master/nftables_test.go#L81
+func ifname(n string) []byte {
+       buf := make([]byte, 16)
+       length := len(n)
+       // allow wildcards
+       if n[length-1:] == "*" {
+               return []byte(n[:length-1])
+       }
+       copy(buf, []byte(n+"\x00"))
+       return buf
+}
diff --git a/daemon/firewall/nftables/exprs/iface_test.go b/daemon/firewall/nftables/exprs/iface_test.go
new file mode 100644 (file)
index 0000000..17a47c4
--- /dev/null
@@ -0,0 +1,80 @@
+package exprs_test
+
+import (
+       "bytes"
+       "reflect"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/expr"
+)
+
+// https://github.com/evilsocket/opensnitch/blob/master/daemon/firewall/nftables/exprs/iface.go#L22
+func ifname(n string) []byte {
+       buf := make([]byte, 16)
+       length := len(n)
+       // allow wildcards
+       if n[length-1:] == "*" {
+               return []byte(n[:length-1])
+       }
+       copy(buf, []byte(n+"\x00"))
+       return buf
+}
+
+func TestExprIface(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       type ifaceTestsT struct {
+               name  string
+               iface string
+               out   bool
+       }
+       tests := []ifaceTestsT{
+               {"test-in-iface-xxx", "in-iface0", false},
+               {"test-out-iface-xxx", "out-iface0", true},
+               {"test-out-iface-xxx-wildcard", "out-iface*", true},
+       }
+
+       for _, test := range tests {
+               t.Run(test.name, func(t *testing.T) {
+                       ifaceExpr := exprs.NewExprIface(test.iface, test.out, expr.CmpOpEq)
+                       r, _ := nftest.AddTestRule(t, conn, ifaceExpr)
+                       if r == nil {
+                               t.Error("Error adding rule with iface expression")
+                       }
+                       if total := len(r.Exprs); total != 2 {
+                               t.Errorf("expected 2 expressions, got %d: %+v", total, r.Exprs)
+                       }
+                       e := r.Exprs[0]
+                       if reflect.TypeOf(e).String() != "*expr.Meta" {
+                               t.Errorf("first expression should be *expr.Meta, instead of: %s", reflect.TypeOf(e))
+                       }
+                       lExpr, ok := e.(*expr.Meta)
+                       if !ok {
+                               t.Errorf("invalid iface meta expr: %T", e)
+                       }
+                       if test.out && lExpr.Key != expr.MetaKeyOIFNAME {
+                               t.Errorf("iface Key should be MetaKeyOIFNAME instead of: %+v", lExpr)
+                       } else if !test.out && lExpr.Key != expr.MetaKeyIIFNAME {
+                               t.Errorf("iface Key should be MetaKeyIIFNAME instead of: %+v", lExpr)
+                       }
+
+                       e = r.Exprs[1]
+                       if reflect.TypeOf(e).String() != "*expr.Cmp" {
+                               t.Errorf("second expression should be *expr.Cmp, instead of: %s", reflect.TypeOf(e))
+                       }
+                       lCmp, ok := e.(*expr.Cmp)
+                       if !ok {
+                               t.Errorf("invalid iface cmp expr: %T", e)
+                       }
+                       if !bytes.Equal(lCmp.Data, ifname(test.iface)) {
+                               t.Errorf("iface Cmp does not match: %v, expected: %v", lCmp.Data, ifname(test.iface))
+                       }
+               })
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/ip.go b/daemon/firewall/nftables/exprs/ip.go
new file mode 100644 (file)
index 0000000..c11f5af
--- /dev/null
@@ -0,0 +1,147 @@
+package exprs
+
+import (
+       "fmt"
+       "net"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+// NewExprIP returns a new IP expression.
+// You can use multiple statements to specify daddr + saddr, or combine them
+// in a single statement expression:
+// Example 1 (filtering by source and dest address):
+// "Name": "ip",
+// "Values": [ {"Key": "saddr": "Value": "1.2.3.4"},{"Key": "daddr": "Value": "1.2.3.5"} ]
+// Example 2 (filtering by multiple dest addrs IPs):
+// "Name": "ip",
+// "Values": [
+//   {"Key": "daddr": "Value": "1.2.3.4"},
+//   {"Key": "daddr": "Value": "1.2.3.5"}
+// ]
+// Example 3 (filtering by network range):
+// "Name": "ip",
+// "Values": [
+//   {"Key": "daddr": "Value": "1.2.3.4-1.2.9.254"}
+// ]
+// TODO (filter by multiple dest addrs separated by commas):
+// "Values": [
+//   {"Key": "daddr": "Value": "1.2.3.4,1.2.9.254"}
+// ]
+func NewExprIP(family string, ipOptions []*config.ExprValues, cmpOp expr.CmpOp) (*[]expr.Any, error) {
+       var exprIP []expr.Any
+
+       // if the table family is inet, we need to specify the protocol of the IP being added.
+       if family == NFT_FAMILY_INET {
+               exprIP = append(exprIP, &expr.Meta{Key: expr.MetaKeyNFPROTO, Register: 1})
+               exprIP = append(exprIP, &expr.Cmp{Op: expr.CmpOpEq, Register: 1, Data: []byte{unix.NFPROTO_IPV4}})
+       }
+       for _, ipOpt := range ipOptions {
+               // TODO: ipv6
+               switch ipOpt.Key {
+               case NFT_SADDR, NFT_DADDR:
+                       payload := getExprIPPayload(ipOpt.Key)
+                       exprIP = append(exprIP, payload)
+                       if strings.Index(ipOpt.Value, "-") == -1 {
+                               exprIPtemp, err := getExprIP(ipOpt.Value, cmpOp)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               exprIP = append(exprIP, *exprIPtemp...)
+                       } else {
+                               exprIPtemp, err := getExprRangeIP(ipOpt.Value, cmpOp)
+                               if err != nil {
+                                       return nil, err
+                               }
+                               exprIP = append(exprIP, *exprIPtemp...)
+                       }
+
+               case NFT_PROTOCOL:
+                       payload := getExprIPPayload(ipOpt.Key)
+                       exprIP = append(exprIP, payload)
+                       protoCode, err := getProtocolCode(ipOpt.Value)
+                       if err != nil {
+                               return nil, err
+                       }
+                       exprIP = append(exprIP, []expr.Any{
+                               &expr.Cmp{
+                                       Op:       cmpOp,
+                                       Register: 1,
+                                       Data:     []byte{byte(protoCode)},
+                               },
+                       }...)
+               }
+       }
+       return &exprIP, nil
+}
+
+func getExprIPPayload(what string) *expr.Payload {
+
+       switch what {
+       case NFT_PROTOCOL:
+               return &expr.Payload{
+                       DestRegister: 1,
+                       Offset:       9, // daddr
+                       Base:         expr.PayloadBaseNetworkHeader,
+                       Len:          1, // 16 ipv6
+               }
+       case NFT_DADDR:
+               // NOTE 1: if "what" is daddr and SourceRegister is part of the Payload{} expression,
+               // the rule is not added.
+               return &expr.Payload{
+                       DestRegister: 1,
+                       Offset:       16, // daddr
+                       Base:         expr.PayloadBaseNetworkHeader,
+                       Len:          4, // 16 ipv6
+               }
+
+       default:
+               return &expr.Payload{
+                       SourceRegister: 1,
+                       DestRegister:   1,
+                       Offset:         12, // saddr
+                       Base:           expr.PayloadBaseNetworkHeader,
+                       Len:            4, // 16 ipv6
+               }
+       }
+}
+
+// Supported IP types: a.b.c.d, a.b.c.d-w.x.y.z
+// TODO: support IPs separated by commas: a.b.c.d, e.f.g.h,...
+func getExprIP(value string, cmpOp expr.CmpOp) (*[]expr.Any, error) {
+       ip := net.ParseIP(value)
+       if ip == nil {
+               return nil, fmt.Errorf("Invalid IP: %s", value)
+       }
+
+       return &[]expr.Any{
+               &expr.Cmp{
+                       Op:       cmpOp,
+                       Register: 1,
+                       Data:     ip.To4(),
+               },
+       }, nil
+}
+
+// Supported IP types: a.b.c.d, a.b.c.d-w.x.y.z
+// TODO: support IPs separated by commas: a.b.c.d, e.f.g.h,...
+func getExprRangeIP(value string, cmpOp expr.CmpOp) (*[]expr.Any, error) {
+       ips := strings.Split(value, "-")
+       ipSrc := net.ParseIP(ips[0])
+       ipDst := net.ParseIP(ips[1])
+       if ipSrc == nil || ipDst == nil {
+               return nil, fmt.Errorf("Invalid IPs range: %v", ips)
+       }
+
+       return &[]expr.Any{
+               &expr.Range{
+                       Op:       cmpOp,
+                       Register: 1,
+                       FromData: ipSrc.To4(),
+                       ToData:   ipDst.To4(),
+               },
+       }, nil
+}
diff --git a/daemon/firewall/nftables/exprs/ip_test.go b/daemon/firewall/nftables/exprs/ip_test.go
new file mode 100644 (file)
index 0000000..e27b162
--- /dev/null
@@ -0,0 +1,354 @@
+package exprs_test
+
+import (
+       "net"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+func TestExprIP(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-ip-daddr",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1.1.1.1",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Payload{
+                                       SourceRegister: 0,
+                                       DestRegister:   1,
+                                       Offset:         16,
+                                       Base:           expr.PayloadBaseNetworkHeader,
+                                       Len:            4,
+                               },
+                               &expr.Cmp{
+                                       Data: net.ParseIP("1.1.1.1").To4(),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-ip-saddr",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "saddr",
+                                       Value: "1.1.1.1",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Payload{
+                                       SourceRegister: 1,
+                                       DestRegister:   1,
+                                       Offset:         12,
+                                       Base:           expr.PayloadBaseNetworkHeader,
+                                       Len:            4,
+                               },
+                               &expr.Cmp{
+                                       Data: net.ParseIP("1.1.1.1").To4(),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-inet-daddr",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1.1.1.1",
+                               },
+                       },
+                       4,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key: expr.MetaKeyNFPROTO, Register: 1,
+                               },
+                               &expr.Cmp{
+                                       Data: []byte{unix.NFPROTO_IPV4},
+                               },
+                               &expr.Payload{
+                                       SourceRegister: 0,
+                                       DestRegister:   1,
+                                       Offset:         16,
+                                       Base:           expr.PayloadBaseNetworkHeader,
+                                       Len:            4,
+                               },
+                               &expr.Cmp{
+                                       Data: net.ParseIP("1.1.1.1").To4(),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-ip-daddr-invalid",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1.1.1",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-ip-daddr-invalid",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1..1.1.1",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-ip-daddr-invalid",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "www.test.com",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-ip-daddr-invalid",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-inet-saddr",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "saddr",
+                                       Value: "1.1.1.1",
+                               },
+                       },
+                       4,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key: expr.MetaKeyNFPROTO, Register: 1,
+                               },
+                               &expr.Cmp{
+                                       Data: []byte{unix.NFPROTO_IPV4},
+                               },
+                               &expr.Payload{
+                                       SourceRegister: 1,
+                                       DestRegister:   1,
+                                       Offset:         12,
+                                       Base:           expr.PayloadBaseNetworkHeader,
+                                       Len:            4,
+                               },
+                               &expr.Cmp{
+                                       Data: net.ParseIP("1.1.1.1").To4(),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-inet-daddr-invalid",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1..1.1.1",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-inet-saddr-invalid",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "saddr",
+                                       Value: "1..1.1.1",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-inet-range-daddr",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1.1.1.1-2.2.2.2",
+                               },
+                       },
+                       4,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key: expr.MetaKeyNFPROTO, Register: 1,
+                               },
+                               &expr.Cmp{
+                                       Data: []byte{unix.NFPROTO_IPV4},
+                               },
+                               &expr.Payload{
+                                       SourceRegister: 0,
+                                       DestRegister:   1,
+                                       Offset:         16,
+                                       Base:           expr.PayloadBaseNetworkHeader,
+                                       Len:            4,
+                               },
+                               &expr.Range{
+                                       Register: 1,
+                                       FromData: net.ParseIP("1.1.1.1").To4(),
+                                       ToData:   net.ParseIP("2.2.2.2").To4(),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-inet-range-saddr",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "saddr",
+                                       Value: "1.1.1.1-2.2.2.2",
+                               },
+                       },
+                       4,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key: expr.MetaKeyNFPROTO, Register: 1,
+                               },
+                               &expr.Cmp{
+                                       Data: []byte{unix.NFPROTO_IPV4},
+                               },
+                               &expr.Payload{
+                                       SourceRegister: 1,
+                                       DestRegister:   1,
+                                       Offset:         12,
+                                       Base:           expr.PayloadBaseNetworkHeader,
+                                       Len:            4,
+                               },
+                               &expr.Range{
+                                       Register: 1,
+                                       FromData: net.ParseIP("1.1.1.1").To4(),
+                                       ToData:   net.ParseIP("2.2.2.2").To4(),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-inet-daddr-range-invalid",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1.1.1.1--2.2.2.2",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-inet-daddr-range-invalid",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "daddr",
+                                       Value: "1.1.1.1-1..2.2.2",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-inet-daddr-range-invalid",
+                       exprs.NFT_FAMILY_INET,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key: "daddr",
+                                       // TODO: not supported yet
+                                       Value: "1.1.1.1/24",
+                               },
+                       },
+                       0,
+                       []interface{}{},
+                       true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+                       ipExpr, err := exprs.NewExprIP(test.Family, test.Values, expr.CmpOpEq)
+                       if err != nil && !test.ExpectedFail {
+                               t.Errorf("Error creating expr IP: %s", ipExpr)
+                               return
+                       } else if err != nil && test.ExpectedFail {
+                               return
+                       }
+
+                       r, _ := nftest.AddTestRule(t, conn, ipExpr)
+                       if r == nil && !test.ExpectedFail {
+                               t.Error("Error adding rule with IP expression")
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+               })
+       }
+
+}
diff --git a/daemon/firewall/nftables/exprs/limit.go b/daemon/firewall/nftables/exprs/limit.go
new file mode 100644 (file)
index 0000000..0b1b4e1
--- /dev/null
@@ -0,0 +1,83 @@
+package exprs
+
+import (
+       "fmt"
+       "strconv"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables/expr"
+)
+
+// NewExprLimit returns a new limit expression.
+// limit rate [over] 1/second
+// to express bytes units, we use: 10-mbytes instead of nft's 10 mbytes
+func NewExprLimit(statement *config.ExprStatement) (*[]expr.Any, error) {
+       var err error
+       exprLimit := &expr.Limit{
+               Type: expr.LimitTypePkts,
+               Over: false,
+               Unit: expr.LimitTimeSecond,
+       }
+
+       for _, values := range statement.Values {
+               switch values.Key {
+
+               case NFT_LIMIT_OVER:
+                       exprLimit.Over = true
+
+               case NFT_LIMIT_UNITS:
+                       exprLimit.Rate, err = strconv.ParseUint(values.Value, 10, 64)
+                       if err != nil {
+                               return nil, fmt.Errorf("Invalid limit rate: %s", values.Value)
+                       }
+
+               case NFT_LIMIT_BURST:
+                       limitBurst := 0
+                       limitBurst, err = strconv.Atoi(values.Value)
+                       if err != nil || limitBurst == 0 {
+                               return nil, fmt.Errorf("Invalid burst limit: %s, err: %s", values.Value, err)
+                       }
+                       exprLimit.Burst = uint32(limitBurst)
+
+               case NFT_LIMIT_UNITS_RATE:
+                       // units rate must be placed AFTER the rate
+                       exprLimit.Type, exprLimit.Rate = getLimitRate(values.Value, exprLimit.Rate)
+
+               case NFT_LIMIT_UNITS_TIME:
+                       exprLimit.Unit = getLimitUnits(values.Value)
+               }
+       }
+
+       return &[]expr.Any{exprLimit}, nil
+}
+
+func getLimitUnits(units string) (limitUnits expr.LimitTime) {
+       switch units {
+       case NFT_LIMIT_UNIT_MINUTE:
+               limitUnits = expr.LimitTimeMinute
+       case NFT_LIMIT_UNIT_HOUR:
+               limitUnits = expr.LimitTimeHour
+       case NFT_LIMIT_UNIT_DAY:
+               limitUnits = expr.LimitTimeDay
+       default:
+               limitUnits = expr.LimitTimeSecond
+       }
+
+       return limitUnits
+}
+
+func getLimitRate(units string, rate uint64) (limitType expr.LimitType, limitRate uint64) {
+       switch units {
+       case NFT_LIMIT_UNIT_KBYTES:
+               limitRate = rate * 1024
+               limitType = expr.LimitTypePktBytes
+       case NFT_LIMIT_UNIT_MBYTES:
+               limitRate = (rate * 1024) * 1024
+               limitType = expr.LimitTypePktBytes
+       default:
+               limitType = expr.LimitTypePkts
+               limitRate, _ = strconv.ParseUint(units, 10, 64)
+       }
+
+       return
+}
diff --git a/daemon/firewall/nftables/exprs/log.go b/daemon/firewall/nftables/exprs/log.go
new file mode 100644 (file)
index 0000000..10fdca2
--- /dev/null
@@ -0,0 +1,73 @@
+package exprs
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+// NewExprLog returns a new log expression.
+func NewExprLog(statement *config.ExprStatement) (*[]expr.Any, error) {
+       prefix := "opensnitch"
+       logExpr := expr.Log{
+               Key:  1 << unix.NFTA_LOG_PREFIX,
+               Data: []byte(prefix),
+       }
+
+       for _, values := range statement.Values {
+               switch values.Key {
+               case NFT_LOG_PREFIX:
+                       if values.Value == "" {
+                               return nil, fmt.Errorf("Invalid log prefix, it's empty")
+                       }
+                       logExpr.Data = []byte(values.Value)
+               case NFT_LOG_LEVEL:
+                       lvl, err := getLogLevel(values.Value)
+                       if err != nil {
+                               log.Warning("%s", err)
+                               return nil, err
+                       }
+                       logExpr.Key |= 1 << unix.NFTA_LOG_LEVEL
+                       logExpr.Level = lvl
+                       // TODO
+                       // https://github.com/google/nftables/blob/main/nftables_test.go#L623
+                       //case exprs.NFT_LOG_FLAGS:
+                       //case exprs.NFT_LOG_GROUP:
+                       //case exprs.NFT_LOG_QTHRESHOLD:
+               }
+       }
+
+       return &[]expr.Any{
+               &logExpr,
+       }, nil
+
+}
+
+func getLogLevel(what string) (expr.LogLevel, error) {
+       switch what {
+       // https://github.com/google/nftables/blob/main/expr/log.go#L28
+       case NFT_LOG_LEVEL_EMERG:
+               return expr.LogLevelEmerg, nil
+       case NFT_LOG_LEVEL_ALERT:
+               return expr.LogLevelAlert, nil
+       case NFT_LOG_LEVEL_CRIT:
+               return expr.LogLevelCrit, nil
+       case NFT_LOG_LEVEL_ERR:
+               return expr.LogLevelErr, nil
+       case NFT_LOG_LEVEL_WARN:
+               return expr.LogLevelWarning, nil
+       case NFT_LOG_LEVEL_NOTICE:
+               return expr.LogLevelNotice, nil
+       case NFT_LOG_LEVEL_INFO:
+               return expr.LogLevelInfo, nil
+       case NFT_LOG_LEVEL_DEBUG:
+               return expr.LogLevelDebug, nil
+       case NFT_LOG_LEVEL_AUDIT:
+               return expr.LogLevelAudit, nil
+       }
+
+       return 0, fmt.Errorf("Invalid log level: %s", what)
+}
diff --git a/daemon/firewall/nftables/exprs/log_test.go b/daemon/firewall/nftables/exprs/log_test.go
new file mode 100644 (file)
index 0000000..cab2d6f
--- /dev/null
@@ -0,0 +1,147 @@
+package exprs_test
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       exprs "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+func TestExprLog(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       type logTestsT struct {
+               nftest.TestsT
+               statem *config.ExprStatement
+       }
+       tests := []logTestsT{
+               {
+                       TestsT: nftest.TestsT{
+                               Name: "test-log-prefix-simple",
+                               Values: []*config.ExprValues{
+                                       &config.ExprValues{
+                                               Key:   "prefix",
+                                               Value: "counter-test",
+                                       },
+                               },
+                               ExpectedExprs: []interface{}{
+                                       &expr.Log{
+                                               Key:  1 << unix.NFTA_LOG_PREFIX,
+                                               Data: []byte("counter-test"),
+                                       },
+                               },
+                               ExpectedExprsNum: 1,
+                               ExpectedFail:     false,
+                       },
+                       statem: &config.ExprStatement{
+                               Op:   "==",
+                               Name: "log",
+                       },
+               },
+               {
+                       TestsT: nftest.TestsT{
+                               Name: "test-log-prefix-emerg",
+                               Values: []*config.ExprValues{
+                                       &config.ExprValues{
+                                               Key:   exprs.NFT_LOG_PREFIX,
+                                               Value: "counter-test-emerg",
+                                       },
+                                       &config.ExprValues{
+                                               Key:   exprs.NFT_LOG_LEVEL,
+                                               Value: exprs.NFT_LOG_LEVEL_EMERG,
+                                       },
+                               },
+                               ExpectedExprs: []interface{}{
+                                       &expr.Log{
+                                               Key:   (1 << unix.NFTA_LOG_PREFIX) | (1 << unix.NFTA_LOG_LEVEL),
+                                               Level: expr.LogLevelEmerg,
+                                               Data:  []byte("counter-test-emerg"),
+                                       },
+                               },
+                               ExpectedExprsNum: 1,
+                               ExpectedFail:     false,
+                       },
+                       statem: &config.ExprStatement{
+                               Op:   "==",
+                               Name: "log",
+                       },
+               },
+               {
+                       TestsT: nftest.TestsT{
+                               Name: "test-invalid-log-prefix",
+                               Values: []*config.ExprValues{
+                                       &config.ExprValues{
+                                               Key:   exprs.NFT_LOG_PREFIX,
+                                               Value: "",
+                                       },
+                                       &config.ExprValues{
+                                               Key:   exprs.NFT_LOG_LEVEL,
+                                               Value: exprs.NFT_LOG_LEVEL_EMERG,
+                                       },
+                               },
+                               ExpectedExprs:    []interface{}{},
+                               ExpectedExprsNum: 0,
+                               ExpectedFail:     true,
+                       },
+                       statem: &config.ExprStatement{
+                               Op:   "==",
+                               Name: "log",
+                       },
+               },
+               {
+                       TestsT: nftest.TestsT{
+                               Name: "test-invalid-log-level",
+                               Values: []*config.ExprValues{
+                                       &config.ExprValues{
+                                               Key:   exprs.NFT_LOG_PREFIX,
+                                               Value: "counter-invalid-level",
+                                       },
+                                       &config.ExprValues{
+                                               Key:   exprs.NFT_LOG_LEVEL,
+                                               Value: "",
+                                       },
+                               },
+                               ExpectedExprs:    []interface{}{},
+                               ExpectedExprsNum: 0,
+                               ExpectedFail:     true,
+                       },
+                       statem: &config.ExprStatement{
+                               Op:   "==",
+                               Name: "log",
+                       },
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+
+                       test.statem.Values = test.TestsT.Values
+                       logExpr, err := exprs.NewExprLog(test.statem)
+                       if err != nil && !test.ExpectedFail {
+                               t.Errorf("Error creating expr Log: %s", logExpr)
+                               return
+                       } else if err != nil && test.ExpectedFail {
+                               return
+                       }
+                       r, _ := nftest.AddTestRule(t, conn, logExpr)
+                       if r == nil {
+                               t.Error("Error adding rule with log expression")
+                       }
+
+                       if !nftest.AreExprsValid(t, &test.TestsT, r) {
+                               return
+                       }
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+
+               })
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/meta.go b/daemon/firewall/nftables/exprs/meta.go
new file mode 100644 (file)
index 0000000..682c4f9
--- /dev/null
@@ -0,0 +1,138 @@
+package exprs
+
+import (
+       "fmt"
+       "strconv"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+)
+
+// NewExprMeta creates a new meta selector to match or set packet metainformation.
+// https://wiki.nftables.org/wiki-nftables/index.php/Matching_packet_metainformation
+func NewExprMeta(values []*config.ExprValues, cmpOp *expr.CmpOp) (*[]expr.Any, error) {
+       setMark := false
+       metaExpr := []expr.Any{}
+
+       for _, meta := range values {
+               switch meta.Key {
+               case NFT_META_SET_MARK:
+                       setMark = true
+                       continue
+               case NFT_META_MARK:
+                       metaKey, err := getMetaKey(meta.Key)
+                       if err != nil {
+                               return nil, err
+                       }
+                       metaVal, err := getMetaValue(meta.Value)
+                       if err != nil {
+                               return nil, err
+                       }
+                       if setMark {
+                               metaExpr = append(metaExpr, []expr.Any{
+                                       &expr.Immediate{
+                                               Register: 1,
+                                               Data:     binaryutil.NativeEndian.PutUint32(uint32(metaVal)),
+                                       }}...)
+                               metaExpr = append(metaExpr, []expr.Any{
+                                       &expr.Meta{Key: metaKey, Register: 1, SourceRegister: setMark}}...)
+                       } else {
+                               metaExpr = append(metaExpr, []expr.Any{
+                                       &expr.Meta{Key: metaKey, Register: 1, SourceRegister: setMark},
+                                       &expr.Cmp{
+                                               Op:       *cmpOp,
+                                               Register: 1,
+                                               Data:     binaryutil.NativeEndian.PutUint32(uint32(metaVal)),
+                                       }}...)
+                       }
+
+                       setMark = false
+                       return &metaExpr, nil
+
+               case NFT_META_L4PROTO:
+                       mexpr, err := NewExprProtocol(meta.Key)
+                       if err != nil {
+                               return nil, err
+                       }
+                       metaExpr = append(metaExpr, *mexpr...)
+
+                       return &metaExpr, nil
+
+               case NFT_META_PRIORITY,
+                       NFT_META_SKUID, NFT_META_SKGID,
+                       NFT_META_PROTOCOL:
+
+                       metaKey, err := getMetaKey(meta.Key)
+                       if err != nil {
+                               return nil, err
+                       }
+                       metaVal, err := getProtocolCode(meta.Value)
+                       if err != nil {
+                               return nil, err
+                       }
+                       metaExpr = append(metaExpr, []expr.Any{
+                               &expr.Meta{Key: metaKey, Register: 1, SourceRegister: setMark},
+                               &expr.Cmp{
+                                       Op:       *cmpOp,
+                                       Register: 1,
+                                       Data:     binaryutil.NativeEndian.PutUint32(uint32(metaVal)),
+                               }}...)
+
+                       setMark = false
+                       return &metaExpr, nil
+
+               case NFT_META_NFTRACE:
+                       mark, err := getMetaValue(meta.Value)
+                       if err != nil {
+                               return nil, err
+                       }
+                       if mark != 0 && mark != 1 {
+                               return nil, fmt.Errorf("%s Invalid nftrace value: %d. Only 1 or 0 allowed", "nftables", mark)
+                       }
+                       // TODO: not working yet
+                       return &[]expr.Any{
+                               &expr.Meta{Key: expr.MetaKeyNFTRACE, Register: 1},
+                               &expr.Cmp{
+                                       Op:       *cmpOp,
+                                       Register: 1,
+                                       Data:     binaryutil.NativeEndian.PutUint32(uint32(mark)),
+                               },
+                       }, nil
+
+               default:
+                       // not supported yet
+               }
+       }
+
+       return nil, fmt.Errorf("%s meta keyword not supported yet, open a new issue on github", "nftables")
+}
+
+func getMetaValue(value string) (int, error) {
+       metaVal, err := strconv.Atoi(value)
+       if err != nil {
+               return 0, err
+       }
+       return metaVal, nil
+}
+
+// https://github.com/google/nftables/blob/main/expr/expr.go#L168
+func getMetaKey(value string) (expr.MetaKey, error) {
+       switch value {
+       case NFT_META_MARK:
+               return expr.MetaKeyMARK, nil
+       case NFT_META_PRIORITY:
+               return expr.MetaKeyPRIORITY, nil
+       case NFT_META_SKUID:
+               return expr.MetaKeySKUID, nil
+       case NFT_META_SKGID:
+               return expr.MetaKeySKGID, nil
+       // ip, ip6, arp, vlan
+       case NFT_META_PROTOCOL:
+               return expr.MetaKeyPROTOCOL, nil
+       case NFT_META_L4PROTO:
+               return expr.MetaKeyL4PROTO, nil
+       }
+
+       return expr.MetaKeyPRANDOM, fmt.Errorf("meta key %s not supported (yet)", value)
+}
diff --git a/daemon/firewall/nftables/exprs/meta_test.go b/daemon/firewall/nftables/exprs/meta_test.go
new file mode 100644 (file)
index 0000000..478278c
--- /dev/null
@@ -0,0 +1,213 @@
+package exprs_test
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+)
+
+func TestExprMeta(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-meta-mark",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_MARK,
+                                       Value: "666",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key:            expr.MetaKeyMARK,
+                                       Register:       1,
+                                       SourceRegister: false,
+                               },
+                               &expr.Cmp{
+                                       Data: binaryutil.NativeEndian.PutUint32(uint32(666)),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-meta-set-mark",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_SET_MARK,
+                                       Value: "",
+                               },
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_MARK,
+                                       Value: "666",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     binaryutil.NativeEndian.PutUint32(666),
+                               },
+                               &expr.Meta{
+                                       Key:            expr.MetaKeyMARK,
+                                       Register:       1,
+                                       SourceRegister: true,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-meta-priority",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_PRIORITY,
+                                       Value: "1",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key:            expr.MetaKeyPRIORITY,
+                                       Register:       1,
+                                       SourceRegister: false,
+                               },
+                               &expr.Cmp{
+                                       Data: binaryutil.NativeEndian.PutUint32(uint32(1)),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-meta-skuid",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_SKUID,
+                                       Value: "1",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key:            expr.MetaKeySKUID,
+                                       Register:       1,
+                                       SourceRegister: false,
+                               },
+                               &expr.Cmp{
+                                       Data: binaryutil.NativeEndian.PutUint32(uint32(1)),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-meta-skgid",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_SKGID,
+                                       Value: "1",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key:            expr.MetaKeySKGID,
+                                       Register:       1,
+                                       SourceRegister: false,
+                               },
+                               &expr.Cmp{
+                                       Data: binaryutil.NativeEndian.PutUint32(uint32(1)),
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-meta-protocol",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_PROTOCOL,
+                                       Value: "15",
+                               },
+                       },
+                       2,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key:            expr.MetaKeyPROTOCOL,
+                                       Register:       1,
+                                       SourceRegister: false,
+                               },
+                               &expr.Cmp{
+                                       Data: binaryutil.NativeEndian.PutUint32(uint32(15)),
+                               },
+                       },
+                       false,
+               },
+               // tested more in depth in protocol_test.go
+               {
+                       "test-meta-l4proto",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_META_L4PROTO,
+                                       Value: "15",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Meta{
+                                       Key:            expr.MetaKeyL4PROTO,
+                                       Register:       1,
+                                       SourceRegister: false,
+                               },
+                       },
+                       false,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+                       cmp := expr.CmpOpEq
+                       metaExpr, err := exprs.NewExprMeta(test.Values, &cmp)
+                       if err != nil && !test.ExpectedFail {
+                               t.Errorf("Error creating expr Meta: %s", metaExpr)
+                               return
+                       } else if err != nil && test.ExpectedFail {
+                               return
+                       }
+
+                       r, _ := nftest.AddTestRule(t, conn, metaExpr)
+                       if r == nil && !test.ExpectedFail {
+                               t.Error("Error adding rule with Meta expression")
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+               })
+       }
+
+}
diff --git a/daemon/firewall/nftables/exprs/nat.go b/daemon/firewall/nftables/exprs/nat.go
new file mode 100644 (file)
index 0000000..207eb38
--- /dev/null
@@ -0,0 +1,152 @@
+package exprs
+
+import (
+       "fmt"
+       "net"
+       "strconv"
+       "strings"
+
+       "github.com/google/nftables"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+// NewExprNATFlags returns the nat flags configured.
+// common to masquerade, snat and dnat
+func NewExprNATFlags(parms string) (random, fullrandom, persistent bool) {
+       masqParms := strings.Split(parms, ",")
+       for _, mParm := range masqParms {
+               switch mParm {
+               case NFT_MASQ_RANDOM:
+                       random = true
+               case NFT_MASQ_FULLY_RANDOM:
+                       fullrandom = true
+               case NFT_MASQ_PERSISTENT:
+                       persistent = true
+               }
+       }
+
+       return
+}
+
+// NewExprNAT parses the redirection of redirect, snat, dnat, tproxy and masquerade verdict:
+// to x.y.z.a:abcd
+// If only the IP is specified (to 1.2.3.4), only NAT.RegAddrMin must be present (regAddr == true)
+// If only the port is specified (to :1234), only NAT.RegPortMin must be present (regPort == true)
+// If both addr and port are specified (to 1.2.3.4:1234), NAT.RegPortMin and NAT.RegAddrMin must be present.
+func NewExprNAT(parms, verdict string) (bool, bool, *[]expr.Any, error) {
+       regAddr := false
+       regProto := false
+       exprNAT := []expr.Any{}
+       NATParms := strings.Split(parms, " ")
+
+       idx := 0
+       // exclude first parameter if it's "to"
+       if NATParms[idx] == NFT_PARM_TO {
+               idx++
+       }
+       if idx == len(NATParms) {
+               return regAddr, regProto, &exprNAT, fmt.Errorf("Invalid parms: %s", parms)
+       }
+
+       dParms := strings.Split(NATParms[idx], ":")
+       // masquerade doesn't allow "to IP"
+       if dParms[0] != "" && verdict != VERDICT_MASQUERADE {
+               dIP := dParms[0]
+               destIP := net.ParseIP(dIP)
+               if destIP == nil {
+                       return regAddr, regProto, &exprNAT, fmt.Errorf("Invalid IP: %s", dIP)
+               }
+
+               exprNAT = append(exprNAT, []expr.Any{
+                       &expr.Immediate{
+                               Register: 1,
+                               Data:     destIP.To4(),
+                       }}...)
+               regAddr = true
+       }
+
+       if len(dParms) == 2 {
+               dPort := dParms[1]
+               // TODO: support ranges. 9000-9100
+               destPort, err := strconv.Atoi(dPort)
+               if err != nil {
+                       return regAddr, regProto, &exprNAT, fmt.Errorf("Invalid Port: %s", dPort)
+               }
+               reg := uint32(2)
+               toPort := binaryutil.BigEndian.PutUint16(uint16(destPort))
+               // if reg=1 (RegAddrMin=1) is not set, this error appears listing the rules
+               // "netlink: Error: NAT statement has no proto expression"
+               if verdict == VERDICT_TPROXY || verdict == VERDICT_MASQUERADE || verdict == VERDICT_REDIRECT {
+                       // according to https://github.com/google/nftables/blob/8a10f689006bf728a5cff35787713047f68e308a/nftables_test.go#L4871
+                       // Masquerade ports should be specified like this:
+                       // toPort = binaryutil.BigEndian.PutUint32(uint32(destPort) << 16)
+                       // but then it's not added/listed correctly with nft.
+
+                       reg = 1
+               }
+               exprNAT = append(exprNAT, []expr.Any{
+                       &expr.Immediate{
+                               Register: reg,
+                               Data:     toPort,
+                       }}...)
+               regProto = true
+       }
+
+       return regAddr, regProto, &exprNAT, nil
+}
+
+// NewExprMasquerade returns a new masquerade expression.
+func NewExprMasquerade(toPorts, random, fullRandom, persistent bool) *[]expr.Any {
+       exprMasq := &expr.Masq{
+               ToPorts:     toPorts,
+               Random:      random,
+               FullyRandom: fullRandom,
+               Persistent:  persistent,
+       }
+       if toPorts {
+               exprMasq.RegProtoMin = 1
+       }
+       return &[]expr.Any{
+               exprMasq,
+       }
+}
+
+// NewExprRedirect returns a new redirect expression.
+func NewExprRedirect() *[]expr.Any {
+       return &[]expr.Any{
+               // Redirect is a special case of DNAT where the destination is the current machine
+               &expr.Redir{
+                       RegisterProtoMin: 1,
+               },
+       }
+}
+
+// NewExprSNAT returns a new snat expression.
+func NewExprSNAT() *expr.NAT {
+       return &expr.NAT{
+               Type:   expr.NATTypeSourceNAT,
+               Family: unix.NFPROTO_IPV4,
+       }
+}
+
+// NewExprDNAT returns a new dnat expression.
+func NewExprDNAT() *expr.NAT {
+       return &expr.NAT{
+               Type:   expr.NATTypeDestNAT,
+               Family: unix.NFPROTO_IPV4,
+       }
+}
+
+// NewExprTproxy returns a new tproxy expression.
+// XXX: is "to x.x.x.x:1234" supported by google/nftables lib? or only "to :1234"?
+// it creates an erronous rule.
+func NewExprTproxy() *[]expr.Any {
+       return &[]expr.Any{
+               &expr.TProxy{
+                       Family:      byte(nftables.TableFamilyIPv4),
+                       TableFamily: byte(nftables.TableFamilyIPv4),
+                       RegPort:     1,
+               }}
+}
diff --git a/daemon/firewall/nftables/exprs/nat_test.go b/daemon/firewall/nftables/exprs/nat_test.go
new file mode 100644 (file)
index 0000000..ec8ce67
--- /dev/null
@@ -0,0 +1,627 @@
+package exprs_test
+
+import (
+       "net"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+func TestExprVerdictSNAT(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       // TODO: test random, permanent, persistent flags.
+       tests := []nftest.TestsT{
+               {
+                       "test-nat-snat-to-127001",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeSourceNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-snat-127001",
+                       exprs.NFT_FAMILY_IP,
+                       "127.0.0.1",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeSourceNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-snat-to-127001:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1:12345",
+                       nil,
+                       3,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.Immediate{
+                                       Register: uint32(2),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeSourceNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                                       RegProtoMin: 2,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-snat-to-:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to :12345",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: uint32(2),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeSourceNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  0,
+                                       RegProtoMin: 2,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-snat-127001:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "127.0.0.1:12345",
+                       nil,
+                       3,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.Immediate{
+                                       Register: uint32(2),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeSourceNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                                       RegProtoMin: 2,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-invalid-nat-snat-to-",
+                       exprs.NFT_FAMILY_IP,
+                       "to",
+                       nil,
+                       3,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-nat-snat-to-invalid-ip",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127..0.0.1",
+                       nil,
+                       3,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-nat-snat-to-invalid-port",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1:aaa",
+                       nil,
+                       3,
+                       []interface{}{},
+                       true,
+               },
+       }
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+
+                       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_SNAT, test.Parms)
+                       if !test.ExpectedFail && verdExpr == nil {
+                               t.Errorf("error creating snat verdict")
+                       } else if test.ExpectedFail && verdExpr == nil {
+                               return
+                       }
+                       r, _ := nftest.AddTestSNATRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule")
+                               return
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+
+               })
+       }
+}
+
+func TestExprVerdictDNAT(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-nat-dnat-to-127001",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeDestNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-dnat-127001",
+                       exprs.NFT_FAMILY_IP,
+                       "127.0.0.1",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeDestNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-dnat-to-127001:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1:12345",
+                       nil,
+                       3,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.Immediate{
+                                       Register: uint32(2),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeDestNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                                       RegProtoMin: 2,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-dnat-to-:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to :12345",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: uint32(2),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeDestNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  0,
+                                       RegProtoMin: 2,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-dnat-127001:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "127.0.0.1:12345",
+                       nil,
+                       3,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.Immediate{
+                                       Register: uint32(2),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.NAT{
+                                       Type:        expr.NATTypeDestNAT,
+                                       Family:      unix.NFPROTO_IPV4,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                                       RegAddrMin:  1,
+                                       RegProtoMin: 2,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-invalid-nat-dnat-to-",
+                       exprs.NFT_FAMILY_IP,
+                       "to",
+                       nil,
+                       3,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-nat-dnat-to-invalid-ip",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127..0.0.1",
+                       nil,
+                       3,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-nat-dnat-to-invalid-port",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1:aaa",
+                       nil,
+                       3,
+                       []interface{}{},
+                       true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+
+                       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_DNAT, test.Parms)
+                       if !test.ExpectedFail && verdExpr == nil {
+                               t.Errorf("error creating verdict")
+                       } else if test.ExpectedFail && verdExpr == nil {
+                               return
+                       }
+                       r, _ := nftest.AddTestDNATRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule")
+                               return
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+
+               })
+       }
+}
+
+func TestExprVerdictMasquerade(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-nat-masq-to-:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to :12345",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: uint32(1),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.Masq{
+                                       ToPorts:     true,
+                                       Random:      false,
+                                       FullyRandom: false,
+                                       Persistent:  false,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-masq-flags",
+                       exprs.NFT_FAMILY_IP,
+                       "random,fully-random,persistent",
+                       nil,
+                       1,
+                       []interface{}{
+                               &expr.Masq{
+                                       ToPorts:     false,
+                                       Random:      true,
+                                       FullyRandom: true,
+                                       Persistent:  true,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-masq-empty",
+                       exprs.NFT_FAMILY_IP,
+                       "",
+                       nil,
+                       1,
+                       []interface{}{
+                               &expr.Masq{},
+                       },
+                       false,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+
+                       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_MASQUERADE, test.Parms)
+                       if !test.ExpectedFail && verdExpr == nil {
+                               t.Errorf("error creating verdict")
+                       } else if test.ExpectedFail && verdExpr == nil {
+                               return
+                       }
+                       r, _ := nftest.AddTestSNATRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule")
+                               return
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+
+               })
+       }
+}
+
+func TestExprVerdictRedirect(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-nat-redir-to-127001:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1:12345",
+                       nil,
+                       3,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.Immediate{
+                                       Register: uint32(1),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.Redir{
+                                       RegisterProtoMin: 1,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-redir-to-:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to :12345",
+                       nil,
+                       2,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: uint32(1),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.Redir{
+                                       RegisterProtoMin: 1,
+                               },
+                       },
+                       false,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+
+                       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_REDIRECT, test.Parms)
+                       if !test.ExpectedFail && verdExpr == nil {
+                               t.Errorf("error creating verdict")
+                       } else if test.ExpectedFail && verdExpr == nil {
+                               return
+                       }
+                       r, _ := nftest.AddTestDNATRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule")
+                               return
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+
+               })
+       }
+}
+
+func TestExprVerdictTProxy(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-nat-tproxy-to-127001:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to 127.0.0.1:12345",
+                       nil,
+                       4,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: 1,
+                                       Data:     net.ParseIP("127.0.0.1").To4(),
+                               },
+                               &expr.Immediate{
+                                       Register: uint32(1),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.TProxy{
+                                       Family:      byte(nftables.TableFamilyIPv4),
+                                       TableFamily: byte(nftables.TableFamilyIPv4),
+                                       RegPort:     1,
+                               },
+                               &expr.Verdict{
+                                       Kind: expr.VerdictAccept,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-nat-tproxy-to-:12345",
+                       exprs.NFT_FAMILY_IP,
+                       "to :12345",
+                       nil,
+                       3,
+                       []interface{}{
+                               &expr.Immediate{
+                                       Register: uint32(1),
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(12345)),
+                               },
+                               &expr.TProxy{
+                                       Family:      byte(nftables.TableFamilyIPv4),
+                                       TableFamily: byte(nftables.TableFamilyIPv4),
+                                       RegPort:     1,
+                               },
+                               &expr.Verdict{
+                                       Kind: expr.VerdictAccept,
+                               },
+                       },
+                       false,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+
+                       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_TPROXY, test.Parms)
+                       if !test.ExpectedFail && verdExpr == nil {
+                               t.Errorf("error creating verdict")
+                       } else if test.ExpectedFail && verdExpr == nil {
+                               return
+                       }
+                       r, _ := nftest.AddTestDNATRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule")
+                               return
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+
+               })
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/notrack.go b/daemon/firewall/nftables/exprs/notrack.go
new file mode 100644 (file)
index 0000000..aa68bdd
--- /dev/null
@@ -0,0 +1,10 @@
+package exprs
+
+import "github.com/google/nftables/expr"
+
+// NewNoTrack adds a new expression not to track connections.
+func NewNoTrack() *[]expr.Any {
+       return &[]expr.Any{
+               &expr.Notrack{},
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/operator.go b/daemon/firewall/nftables/exprs/operator.go
new file mode 100644 (file)
index 0000000..3fc1e6d
--- /dev/null
@@ -0,0 +1,33 @@
+package exprs
+
+import (
+       "github.com/google/nftables/expr"
+)
+
+// NewOperator translates a string comparator operator to nftables operator
+func NewOperator(operator string) expr.CmpOp {
+       switch operator {
+       case "!=":
+               return expr.CmpOpNeq
+       case ">":
+               return expr.CmpOpGt
+       case ">=":
+               return expr.CmpOpGte
+       case "<":
+               return expr.CmpOpLt
+       case "<=":
+               return expr.CmpOpLte
+       }
+
+       return expr.CmpOpEq
+}
+
+// NewExprOperator returns a new comparator operator
+func NewExprOperator(op expr.CmpOp) *[]expr.Any {
+       return &[]expr.Any{
+               &expr.Cmp{
+                       Register: 1,
+                       Op:       op,
+               },
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/port.go b/daemon/firewall/nftables/exprs/port.go
new file mode 100644 (file)
index 0000000..220572a
--- /dev/null
@@ -0,0 +1,97 @@
+package exprs
+
+import (
+       "fmt"
+       "strconv"
+       "strings"
+
+       "github.com/google/nftables"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+)
+
+// NewExprPort returns a new port expression with the given matching operator.
+func NewExprPort(port string, op *expr.CmpOp) (*[]expr.Any, error) {
+       eport, err := strconv.Atoi(port)
+       if err != nil {
+               return nil, err
+       }
+       return &[]expr.Any{
+               &expr.Cmp{
+                       Register: 1,
+                       Op:       *op,
+                       Data:     binaryutil.BigEndian.PutUint16(uint16(eport))},
+       }, nil
+}
+
+// NewExprPortRange returns a new port range expression.
+func NewExprPortRange(sport string, cmpOp *expr.CmpOp) (*[]expr.Any, error) {
+       ports := strings.Split(sport, "-")
+       iport, err := strconv.Atoi(ports[0])
+       if err != nil {
+               return nil, err
+       }
+       eport, err := strconv.Atoi(ports[1])
+       if err != nil {
+               return nil, err
+       }
+       return &[]expr.Any{
+               &expr.Range{
+                       Op:       *cmpOp,
+                       Register: 1,
+                       FromData: binaryutil.BigEndian.PutUint16(uint16(iport)),
+                       ToData:   binaryutil.BigEndian.PutUint16(uint16(eport)),
+               },
+       }, nil
+
+}
+
+// NewExprPortSet returns a new set of ports.
+func NewExprPortSet(portv string) *[]nftables.SetElement {
+       setElements := []nftables.SetElement{}
+       ports := strings.Split(portv, ",")
+       for _, portv := range ports {
+               portExpr := exprPortSubSet(portv)
+               if portExpr != nil {
+                       setElements = append(setElements, *portExpr...)
+               }
+       }
+
+       return &setElements
+}
+
+func exprPortSubSet(portv string) *[]nftables.SetElement {
+       port, err := strconv.Atoi(portv)
+       if err != nil {
+               return nil
+       }
+
+       return &[]nftables.SetElement{
+               {Key: binaryutil.BigEndian.PutUint16(uint16(port))},
+       }
+
+}
+
+// NewExprPortDirection returns a new expression to match connections based on
+// the direction of the connection (source, dest)
+func NewExprPortDirection(direction string) (*expr.Payload, error) {
+       switch direction {
+       case NFT_DPORT:
+               return &expr.Payload{
+                       DestRegister: 1,
+                       Base:         expr.PayloadBaseTransportHeader,
+                       Offset:       2,
+                       Len:          2,
+               }, nil
+       case NFT_SPORT:
+               return &expr.Payload{
+                       DestRegister: 1,
+                       Base:         expr.PayloadBaseTransportHeader,
+                       Offset:       0,
+                       Len:          2,
+               }, nil
+       default:
+               return nil, fmt.Errorf("Not valid protocol direction: %s", direction)
+       }
+
+}
diff --git a/daemon/firewall/nftables/exprs/port_test.go b/daemon/firewall/nftables/exprs/port_test.go
new file mode 100644 (file)
index 0000000..553a140
--- /dev/null
@@ -0,0 +1,114 @@
+package exprs_test
+
+import (
+       "bytes"
+       "fmt"
+       "reflect"
+       "testing"
+
+       exprs "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+)
+
+type portTestsT struct {
+       port       string
+       portVal    int
+       cmp        expr.CmpOp
+       shouldFail bool
+}
+
+func TestExprPort(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       portTests := []portTestsT{
+               {"53", 53, expr.CmpOpEq, false},
+               {"80", 80, expr.CmpOpEq, false},
+               {"65535", 65535, expr.CmpOpEq, false},
+               {"45,", 0, expr.CmpOpEq, true},
+               {"", 0, expr.CmpOpEq, true},
+       }
+
+       for _, test := range portTests {
+               t.Run(fmt.Sprint("test-", test.port), func(t *testing.T) {
+                       portExpr, err := exprs.NewExprPort(test.port, &test.cmp)
+                       if err != nil {
+                               if !test.shouldFail {
+                                       t.Errorf("Error creating expr port: %v, %s", test, err)
+                               }
+                               return
+                       }
+                       //fmt.Printf("%s, %+v\n", test.port, *portExpr)
+                       r, _ := nftest.AddTestRule(t, conn, portExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule with port (%s) expression", test.port)
+                       }
+                       e := r.Exprs[0]
+                       cmp, ok := e.(*expr.Cmp)
+                       if !ok {
+                               t.Errorf("%s - invalid port expr: %T", test.port, e)
+                       }
+                       //fmt.Printf("%s, %+v\n", reflect.TypeOf(e).String(), e)
+                       if reflect.TypeOf(e).String() != "*expr.Cmp" {
+                               t.Errorf("%s - first expression should be *expr.Cmp, instead of: %s", test.port, reflect.TypeOf(e))
+                       }
+                       portVal := binaryutil.BigEndian.PutUint16(uint16(test.portVal))
+                       if !bytes.Equal(cmp.Data, portVal) {
+                               t.Errorf("%s - invalid port in expr.Cmp: %d", test.port, cmp.Data)
+                       }
+
+               })
+       }
+}
+
+func TestExprPortRange(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       portTests := []portTestsT{
+               {"53-5353", 53, expr.CmpOpEq, false},
+               {"80-8080", 80, expr.CmpOpEq, false},
+               {"1-65535", 65535, expr.CmpOpEq, false},
+               {"1,45,", 0, expr.CmpOpEq, true},
+               {"1-2.", 0, expr.CmpOpEq, true},
+       }
+
+       for _, test := range portTests {
+               t.Run(fmt.Sprint("test-", test.port), func(t *testing.T) {
+                       portExpr, err := exprs.NewExprPortRange(test.port, &test.cmp)
+                       if err != nil {
+                               if !test.shouldFail {
+                                       t.Errorf("Error creating expr port range: %v, %s", test, err)
+                               }
+                               return
+                       }
+                       //fmt.Printf("%s, %+v\n", test.port, *portExpr)
+                       r, _ := nftest.AddTestRule(t, conn, portExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule with port range (%s) expression", test.port)
+                       }
+                       e := r.Exprs[0]
+                       _, ok := e.(*expr.Range)
+                       if !ok {
+                               t.Errorf("%s - invalid port range expr: %T", test.port, e)
+                       }
+                       fmt.Printf("%s, %+v\n", reflect.TypeOf(e).String(), e)
+                       if reflect.TypeOf(e).String() != "*expr.Range" {
+                               t.Errorf("%s - first expression should be *expr.Cmp, instead of: %s", test.port, reflect.TypeOf(e))
+                       }
+                       /*portVal := binaryutil.BigEndian.PutUint16(uint16(test.portVal))
+                       if !bytes.Equal(range.FromData, portVal) {
+                               t.Errorf("%s - invalid port range in expr.Cmp: %d", test.port, cmp.Data)
+                       }*/
+
+               })
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/protocol.go b/daemon/firewall/nftables/exprs/protocol.go
new file mode 100644 (file)
index 0000000..ac039d4
--- /dev/null
@@ -0,0 +1,143 @@
+package exprs
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+// NewExprProtocol creates a new expression to filter connections by protocol
+func NewExprProtocol(proto string) (*[]expr.Any, error) {
+       protoExpr := expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1}
+
+       switch strings.ToLower(proto) {
+       case NFT_META_L4PROTO:
+               return &[]expr.Any{
+                       &protoExpr,
+               }, nil
+
+       case NFT_PROTO_UDP:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_UDP},
+                       },
+               }, nil
+
+       case NFT_PROTO_TCP:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_TCP},
+                       },
+               }, nil
+
+       case NFT_PROTO_UDPLITE:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_UDPLITE},
+                       },
+               }, nil
+
+       case NFT_PROTO_SCTP:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_SCTP},
+                       },
+               }, nil
+
+       case NFT_PROTO_DCCP:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_DCCP},
+                       },
+               }, nil
+
+       case NFT_PROTO_ICMP:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_ICMP},
+                       },
+               }, nil
+
+       case NFT_PROTO_ICMPv6:
+               return &[]expr.Any{
+                       &protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_ICMPV6},
+                       },
+               }, nil
+
+               /*TODO: could be simplified
+               default:
+               proto, err := getProtocolCode(value)
+               if err != nil {
+                       return nil, err
+               }
+               return &[]expr.Any{
+                       protoExpr,
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{byte(proto)},
+                       },
+               }, nil
+               */
+       default:
+               return nil, fmt.Errorf("Not valid protocol rule, invalid or not supported protocol: %s", proto)
+       }
+
+}
+
+// NewExprProtoSet creates a new list of SetElements{}, to match
+// multiple protocol values.
+func NewExprProtoSet(l4prots string) *[]nftables.SetElement {
+       protoList := strings.Split(l4prots, ",")
+       protoSet := []nftables.SetElement{}
+       for _, name := range protoList {
+               pcode, err := getProtocolCode(name)
+               if err != nil {
+                       continue
+               }
+
+               protoSet = append(protoSet,
+                       []nftables.SetElement{
+                               {Key: []byte{byte(pcode)}},
+                       }...)
+       }
+
+       return &protoSet
+}
+
+// NewExprL4Proto returns a new expression to match a protocol.
+func NewExprL4Proto(name string, cmpOp *expr.CmpOp) *[]expr.Any {
+       proto, _ := getProtocolCode(name)
+       return &[]expr.Any{
+               &expr.Cmp{
+                       Op:       *cmpOp,
+                       Register: 1,
+                       Data:     []byte{byte(proto)},
+               },
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/protocol_test.go b/daemon/firewall/nftables/exprs/protocol_test.go
new file mode 100644 (file)
index 0000000..b2ed968
--- /dev/null
@@ -0,0 +1,84 @@
+package exprs_test
+
+import (
+       "fmt"
+       "reflect"
+       "testing"
+
+       exprs "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+func TestExprProtocol(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       testProtos := []string{
+               exprs.NFT_PROTO_TCP,
+               exprs.NFT_PROTO_UDP,
+               exprs.NFT_PROTO_UDPLITE,
+               exprs.NFT_PROTO_SCTP,
+               exprs.NFT_PROTO_DCCP,
+               exprs.NFT_PROTO_ICMP,
+               exprs.NFT_PROTO_ICMPv6,
+       }
+       protoValues := []byte{
+               unix.IPPROTO_TCP,
+               unix.IPPROTO_UDP,
+               unix.IPPROTO_UDPLITE,
+               unix.IPPROTO_SCTP,
+               unix.IPPROTO_DCCP,
+               unix.IPPROTO_ICMP,
+               unix.IPPROTO_ICMPV6,
+       }
+
+       for idx, proto := range testProtos {
+               t.Run(fmt.Sprint("test-protoExpr-", proto), func(t *testing.T) {
+                       protoExpr, err := exprs.NewExprProtocol(proto)
+                       if err != nil {
+                               t.Errorf("%s - Error creating expr Log: %s", proto, protoExpr)
+                               return
+                       }
+                       r, _ := nftest.AddTestRule(t, conn, protoExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule with proto %s expression", proto)
+                       }
+                       if len(r.Exprs) != 2 {
+                               t.Errorf("%s - expected 2 Expressions, found %d", proto, len(r.Exprs))
+                       }
+                       e := r.Exprs[0]
+                       meta, ok := e.(*expr.Meta)
+                       if !ok {
+                               t.Errorf("%s - invalid proto expr: %T", proto, e)
+                       }
+                       //fmt.Printf("%s, %+v\n", reflect.TypeOf(e).String(), e)
+                       if reflect.TypeOf(e).String() != "*expr.Meta" {
+                               t.Errorf("%s - first expression should be *expr.Meta, instead of: %s", proto, reflect.TypeOf(e))
+                       }
+                       if meta.Key != expr.MetaKeyL4PROTO {
+                               t.Errorf("%s - invalid proto expr.Meta.Key: %d", proto, expr.MetaKeyL4PROTO)
+                       }
+
+                       e = r.Exprs[1]
+                       cmp, ok := e.(*expr.Cmp)
+                       if !ok {
+                               t.Errorf("%s - invalid proto cmp expr: %T", proto, e)
+                       }
+                       //fmt.Printf("%s, %+v\n", reflect.TypeOf(e).String(), e)
+                       if reflect.TypeOf(e).String() != "*expr.Cmp" {
+                               t.Errorf("%s - second expression should be *expr.Cmp, instead of: %s", proto, reflect.TypeOf(e))
+                       }
+                       if cmp.Op != expr.CmpOpEq {
+                               t.Errorf("%s - expr.Cmp should be CmpOpEq, instead of: %d", proto, cmp.Op)
+                       }
+                       if cmp.Data[0] != protoValues[idx] {
+                               t.Errorf("%s - expr.Data differs: %d<->%d", proto, cmp.Data, protoValues[idx])
+                       }
+               })
+       }
+}
diff --git a/daemon/firewall/nftables/exprs/quota.go b/daemon/firewall/nftables/exprs/quota.go
new file mode 100644 (file)
index 0000000..5d705c7
--- /dev/null
@@ -0,0 +1,66 @@
+package exprs
+
+import (
+       "fmt"
+       "strconv"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables/expr"
+)
+
+// NewQuota returns a new quota expression.
+// TODO: named quotas
+func NewQuota(opts []*config.ExprValues) (*[]expr.Any, error) {
+       over := false
+       bytes := int64(0)
+       used := int64(0)
+       for _, opt := range opts {
+               switch opt.Key {
+               case NFT_QUOTA_OVER:
+                       over = true
+               case NFT_QUOTA_UNIT_BYTES:
+                       b, err := strconv.ParseInt(opt.Value, 10, 64)
+                       if err != nil {
+                               return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
+                       }
+                       bytes = b
+               case NFT_QUOTA_USED:
+                       // TODO: support for other size units
+                       b, err := strconv.ParseInt(opt.Value, 10, 64)
+                       if err != nil {
+                               return nil, fmt.Errorf("invalid quota initial consumed bytes: %s", opt.Value)
+                       }
+                       used = b
+               case NFT_QUOTA_UNIT_KB:
+                       b, err := strconv.ParseInt(opt.Value, 10, 64)
+                       if err != nil {
+                               return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
+                       }
+                       bytes = b * 1024
+               case NFT_QUOTA_UNIT_MB:
+                       b, err := strconv.ParseInt(opt.Value, 10, 64)
+                       if err != nil {
+                               return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
+                       }
+                       bytes = (b * 1024) * 1024
+               case NFT_QUOTA_UNIT_GB:
+                       b, err := strconv.ParseInt(opt.Value, 10, 64)
+                       if err != nil {
+                               return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value)
+                       }
+                       bytes = ((b * 1024) * 1024) * 1024
+               default:
+                       return nil, fmt.Errorf("invalid quota key: %s", opt.Key)
+               }
+       }
+       if bytes == 0 {
+               return nil, fmt.Errorf("quota bytes cannot be 0")
+       }
+       return &[]expr.Any{
+               &expr.Quota{
+                       Bytes:    uint64(bytes),
+                       Consumed: uint64(used),
+                       Over:     over,
+               },
+       }, nil
+}
diff --git a/daemon/firewall/nftables/exprs/quota_test.go b/daemon/firewall/nftables/exprs/quota_test.go
new file mode 100644 (file)
index 0000000..ebb7a91
--- /dev/null
@@ -0,0 +1,243 @@
+package exprs_test
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables/expr"
+)
+
+func TestExprQuota(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       tests := []nftest.TestsT{
+               {
+                       "test-quota-over-bytes-12345",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_OVER,
+                                       Value: "",
+                               },
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_BYTES,
+                                       Value: "12345",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Quota{
+                                       Bytes:    uint64(12345),
+                                       Consumed: 0,
+                                       Over:     true,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-quota-over-kbytes-1",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_OVER,
+                                       Value: "",
+                               },
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_KB,
+                                       Value: "1",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Quota{
+                                       Bytes:    uint64(1024),
+                                       Consumed: 0,
+                                       Over:     true,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-quota-over-mbytes-1",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_OVER,
+                                       Value: "",
+                               },
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_MB,
+                                       Value: "1",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Quota{
+                                       Bytes:    uint64(1024 * 1024),
+                                       Consumed: 0,
+                                       Over:     true,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-quota-over-gbytes-1",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_OVER,
+                                       Value: "",
+                               },
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_GB,
+                                       Value: "1",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Quota{
+                                       Bytes:    uint64(1024 * 1024 * 1024),
+                                       Consumed: 0,
+                                       Over:     true,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-quota-until-gbytes-1",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_GB,
+                                       Value: "1",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Quota{
+                                       Bytes:    uint64(1024 * 1024 * 1024),
+                                       Consumed: 0,
+                                       Over:     false,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-quota-consumed-bytes-1024",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_GB,
+                                       Value: "1",
+                               },
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_USED,
+                                       Value: "1024",
+                               },
+                       },
+                       1,
+                       []interface{}{
+                               &expr.Quota{
+                                       Bytes:    uint64(1024 * 1024 * 1024),
+                                       Consumed: 1024,
+                                       Over:     false,
+                               },
+                       },
+                       false,
+               },
+               {
+                       "test-invalid-quota-key",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   "gbyte",
+                                       Value: "1",
+                               },
+                       },
+                       1,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-quota-value",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_GB,
+                                       Value: "1a",
+                               },
+                       },
+                       1,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-quota-value",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_GB,
+                                       Value: "",
+                               },
+                       },
+                       1,
+                       []interface{}{},
+                       true,
+               },
+               {
+                       "test-invalid-quota-bytes-0",
+                       "", // family
+                       "", // parms
+                       []*config.ExprValues{
+                               &config.ExprValues{
+                                       Key:   exprs.NFT_QUOTA_UNIT_GB,
+                                       Value: "0",
+                               },
+                       },
+                       1,
+                       []interface{}{},
+                       true,
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.Name, func(t *testing.T) {
+                       quotaExpr, err := exprs.NewQuota(test.Values)
+                       if err != nil && !test.ExpectedFail {
+                               t.Errorf("Error creating expr Quota: %s", quotaExpr)
+                               return
+                       } else if err != nil && test.ExpectedFail {
+                               return
+                       }
+
+                       r, _ := nftest.AddTestRule(t, conn, quotaExpr)
+                       if r == nil && !test.ExpectedFail {
+                               t.Error("Error adding rule with Quota expression")
+                       }
+
+                       if !nftest.AreExprsValid(t, &test, r) {
+                               return
+                       }
+
+                       if test.ExpectedFail {
+                               t.Errorf("test should have failed")
+                       }
+               })
+       }
+
+}
diff --git a/daemon/firewall/nftables/exprs/utils.go b/daemon/firewall/nftables/exprs/utils.go
new file mode 100644 (file)
index 0000000..35081cf
--- /dev/null
@@ -0,0 +1,181 @@
+package exprs
+
+import (
+       "strconv"
+
+       "github.com/google/gopacket/layers"
+       "golang.org/x/sys/unix"
+)
+
+// GetICMPRejectCode returns the code by its name.
+func GetICMPRejectCode(reason string) uint8 {
+       switch reason {
+       case ICMP_HOST_UNREACHABLE, ICMP_ADDR_UNREACHABLE:
+               return layers.ICMPv4CodeHost
+       case ICMP_PROT_UNREACHABLE:
+               return layers.ICMPv4CodeProtocol
+       case ICMP_PORT_UNREACHABLE:
+               return layers.ICMPv4CodePort
+       case ICMP_ADMIN_PROHIBITED:
+               return layers.ICMPv4CodeCommAdminProhibited
+       case ICMP_HOST_PROHIBITED:
+               return layers.ICMPv4CodeHostAdminProhibited
+       case ICMP_NET_PROHIBITED:
+               return layers.ICMPv4CodeNetAdminProhibited
+       }
+
+       return layers.ICMPv4CodeNet
+}
+
+// GetICMPxRejectCode returns the code by its name.
+func GetICMPxRejectCode(reason string) uint8 {
+       // https://github.com/torvalds/linux/blob/master/net/netfilter/nft_reject.c#L96
+       // https://github.com/google/gopacket/blob/3aa782ce48d4a525acaebab344cedabfb561f870/layers/icmp4.go#L37
+       switch reason {
+       case ICMP_HOST_UNREACHABLE, ICMP_NET_UNREACHABLE:
+               return unix.NFT_REJECT_ICMP_UNREACH // results in -> net-unreachable???
+       case ICMP_PROT_UNREACHABLE:
+               return unix.NFT_REJECT_ICMPX_HOST_UNREACH // results in -> prot-unreachable???
+       case ICMP_PORT_UNREACHABLE:
+               return unix.NFT_REJECT_ICMPX_PORT_UNREACH // results in -> host-unreachable???
+       case ICMP_NO_ROUTE:
+               return unix.NFT_REJECT_ICMPX_NO_ROUTE // results in -> net-unreachable
+       }
+
+       return unix.NFT_REJECT_ICMP_UNREACH // results in -> net-unreachable???
+}
+
+// GetICMPType returns an ICMP type code
+func GetICMPType(icmpType string) uint8 {
+       switch icmpType {
+       case ICMP_ECHO_REPLY:
+               return layers.ICMPv4TypeEchoReply
+       case ICMP_ECHO_REQUEST:
+               return layers.ICMPv4TypeEchoRequest
+       case ICMP_SOURCE_QUENCH:
+               return layers.ICMPv4TypeSourceQuench
+       case ICMP_DEST_UNREACHABLE:
+               return layers.ICMPv4TypeDestinationUnreachable
+       case ICMP_ROUTER_ADVERTISEMENT:
+               return layers.ICMPv4TypeRouterAdvertisement
+       case ICMP_ROUTER_SOLICITATION:
+               return layers.ICMPv4TypeRouterSolicitation
+       case ICMP_REDIRECT:
+               return layers.ICMPv4TypeRedirect
+       case ICMP_TIME_EXCEEDED:
+               return layers.ICMPv4TypeTimeExceeded
+       case ICMP_INFO_REQUEST:
+               return layers.ICMPv4TypeInfoRequest
+       case ICMP_INFO_REPLY:
+               return layers.ICMPv4TypeInfoReply
+       case ICMP_PARAMETER_PROBLEM:
+               return layers.ICMPv4TypeParameterProblem
+       case ICMP_TIMESTAMP_REQUEST:
+               return layers.ICMPv4TypeTimestampRequest
+       case ICMP_TIMESTAMP_REPLY:
+               return layers.ICMPv4TypeTimestampReply
+       case ICMP_ADDRESS_MASK_REQUEST:
+               return layers.ICMPv4TypeAddressMaskRequest
+       case ICMP_ADDRESS_MASK_REPLY:
+               return layers.ICMPv4TypeAddressMaskReply
+       }
+       return 0
+}
+
+// GetICMPv6Type returns an ICMPv6 type code
+func GetICMPv6Type(icmpType string) uint8 {
+       switch icmpType {
+       case ICMP_DEST_UNREACHABLE:
+               return layers.ICMPv6TypeDestinationUnreachable
+       case ICMP_PACKET_TOO_BIG:
+               return layers.ICMPv6TypePacketTooBig
+       case ICMP_TIME_EXCEEDED:
+               return layers.ICMPv6TypeTimeExceeded
+       case ICMP_PARAMETER_PROBLEM:
+               return layers.ICMPv6TypeParameterProblem
+       case ICMP_ECHO_REQUEST:
+               return layers.ICMPv6TypeEchoRequest
+       case ICMP_ECHO_REPLY:
+               return layers.ICMPv6TypeEchoReply
+       case ICMP_ROUTER_SOLICITATION:
+               return layers.ICMPv6TypeRouterSolicitation
+       case ICMP_ROUTER_ADVERTISEMENT:
+               return layers.ICMPv6TypeRouterAdvertisement
+       case ICMP_NEIGHBOUR_SOLICITATION:
+               return layers.ICMPv6TypeNeighborSolicitation
+       case ICMP_NEIGHBOUR_ADVERTISEMENT:
+               return layers.ICMPv6TypeNeighborAdvertisement
+       case ICMP_REDIRECT:
+               return layers.ICMPv6TypeRedirect
+       }
+       return 0
+}
+
+// GetICMPv6RejectCode returns the code by its name.
+func GetICMPv6RejectCode(reason string) uint8 {
+       switch reason {
+       case ICMP_HOST_UNREACHABLE, ICMP_NET_UNREACHABLE, ICMP_NO_ROUTE:
+               return layers.ICMPv6CodeNoRouteToDst
+       case ICMP_ADDR_UNREACHABLE:
+               return layers.ICMPv6CodeAddressUnreachable
+       case ICMP_PORT_UNREACHABLE:
+               return layers.ICMPv6CodePortUnreachable
+       case ICMP_REJECT_POLICY_FAIL:
+               return layers.ICMPv6CodeSrcAddressFailedPolicy
+       case ICMP_REJECT_ROUTE:
+               return layers.ICMPv6CodeRejectRouteToDst
+       }
+
+       return layers.ICMPv6CodeNoRouteToDst
+}
+
+// getProtocolCode will try to return the code of the given protocol.
+// If the protocol is not in our list, we'll use the value as decimal.
+// So for example IPPROTO_ENCAP (0x62) must be specified as 98.
+// https://pkg.go.dev/golang.org/x/sys/unix#pkg-constants
+func getProtocolCode(value string) (byte, error) {
+       switch value {
+       case NFT_PROTO_TCP:
+               return unix.IPPROTO_TCP, nil
+       case NFT_PROTO_UDP:
+               return unix.IPPROTO_UDP, nil
+       case NFT_PROTO_UDPLITE:
+               return unix.IPPROTO_UDPLITE, nil
+       case NFT_PROTO_SCTP:
+               return unix.IPPROTO_SCTP, nil
+       case NFT_PROTO_DCCP:
+               return unix.IPPROTO_DCCP, nil
+       case NFT_PROTO_ICMP:
+               return unix.IPPROTO_ICMP, nil
+       case NFT_PROTO_ICMPv6:
+               return unix.IPPROTO_ICMPV6, nil
+       case NFT_PROTO_AH:
+               return unix.IPPROTO_AH, nil
+       case NFT_PROTO_ETHERNET:
+               return unix.IPPROTO_ETHERNET, nil
+       case NFT_PROTO_GRE:
+               return unix.IPPROTO_GRE, nil
+       case NFT_PROTO_IP:
+               return unix.IPPROTO_IP, nil
+       case NFT_PROTO_IPIP:
+               return unix.IPPROTO_IPIP, nil
+       case NFT_PROTO_L2TP:
+               return unix.IPPROTO_L2TP, nil
+       case NFT_PROTO_COMP:
+               return unix.IPPROTO_COMP, nil
+       case NFT_PROTO_IGMP:
+               return unix.IPPROTO_IGMP, nil
+       case NFT_PROTO_ESP:
+               return unix.IPPROTO_ESP, nil
+       case NFT_PROTO_RAW:
+               return unix.IPPROTO_RAW, nil
+       case NFT_PROTO_ENCAP:
+               return unix.IPPROTO_ENCAP, nil
+       }
+
+       prot, err := strconv.Atoi(value)
+       if err != nil {
+               return 0, err
+       }
+       return byte(prot), nil
+}
diff --git a/daemon/firewall/nftables/exprs/verdict.go b/daemon/firewall/nftables/exprs/verdict.go
new file mode 100644 (file)
index 0000000..54a0232
--- /dev/null
@@ -0,0 +1,202 @@
+package exprs
+
+import (
+       "strconv"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+// NewExprVerdict constructs a new verdict to apply on connections.
+func NewExprVerdict(verdict, parms string) *[]expr.Any {
+       switch strings.ToLower(verdict) {
+       case VERDICT_ACCEPT:
+               return NewExprAccept()
+
+       case VERDICT_DROP:
+               return &[]expr.Any{&expr.Verdict{
+                       Kind: expr.VerdictDrop,
+               }}
+
+       // FIXME: this verdict is not added to nftables
+       case VERDICT_STOP:
+               return &[]expr.Any{&expr.Verdict{
+                       Kind: expr.VerdictStop,
+               }}
+
+       case VERDICT_REJECT:
+               reject := NewExprReject(parms)
+               return &[]expr.Any{reject}
+
+       case VERDICT_RETURN:
+               return &[]expr.Any{&expr.Verdict{
+                       Kind: expr.VerdictReturn,
+               }}
+
+       case VERDICT_JUMP:
+               return &[]expr.Any{
+                       &expr.Verdict{
+                               Kind:  expr.VerdictKind(unix.NFT_JUMP),
+                               Chain: parms,
+                       },
+               }
+
+       case VERDICT_QUEUE:
+               queueNum := 0
+               var err error
+               p := strings.Split(parms, " ")
+               if len(p) == 0 {
+                       log.Warning("invalid Queue expr parameters")
+                       return nil
+               }
+               // TODO: allow to configure this flag
+               if p[0] == NFT_QUEUE_NUM {
+                       queueNum, err = strconv.Atoi(p[len(p)-1])
+                       if err != nil {
+                               log.Warning("invalid Queue num: %s", err)
+                               return nil
+                       }
+               }
+
+               return &[]expr.Any{
+                       &expr.Queue{
+                               Num:  uint16(queueNum),
+                               Flag: expr.QueueFlagBypass,
+                       }}
+
+       case VERDICT_SNAT:
+               snat := NewExprSNAT()
+               snat.Random, snat.FullyRandom, snat.Persistent = NewExprNATFlags(parms)
+               snatExpr := &[]expr.Any{snat}
+
+               regAddr, regProto, natParms, err := NewExprNAT(parms, VERDICT_SNAT)
+               if err != nil {
+                       log.Warning("error adding snat verdict: %s", err)
+                       return nil
+               }
+               if regAddr {
+                       snat.RegAddrMin = 1
+               }
+               if regProto {
+                       snat.RegProtoMin = 2
+               }
+               *snatExpr = append(*natParms, *snatExpr...)
+               return snatExpr
+
+       case VERDICT_DNAT:
+               dnat := NewExprDNAT()
+               dnat.Random, dnat.FullyRandom, dnat.Persistent = NewExprNATFlags(parms)
+               dnatExpr := &[]expr.Any{dnat}
+
+               regAddr, regProto, natParms, err := NewExprNAT(parms, VERDICT_DNAT)
+               if err != nil {
+                       log.Warning("error adding dnat verdict: %s", err)
+                       return nil
+               }
+
+               if regAddr {
+                       dnat.RegAddrMin = 1
+               }
+               if regProto {
+                       dnat.RegProtoMin = 2
+               }
+               *dnatExpr = append(*natParms, *dnatExpr...)
+
+               return dnatExpr
+
+       case VERDICT_MASQUERADE:
+               m := &expr.Masq{}
+               m.Random, m.FullyRandom, m.Persistent = NewExprNATFlags(parms)
+               masqExpr := &[]expr.Any{m}
+
+               if parms == "" {
+                       return masqExpr
+               }
+               // if any of the flag is set to true, toPorts must be false
+               toPorts := !(m.Random == true || m.FullyRandom == true || m.Persistent == true)
+               masqExpr = NewExprMasquerade(toPorts, m.Random, m.FullyRandom, m.Persistent)
+               _, _, natParms, err := NewExprNAT(parms, VERDICT_MASQUERADE)
+               if err != nil {
+                       log.Warning("error adding masquerade verdict: %s", err)
+               }
+               *masqExpr = append(*natParms, *masqExpr...)
+
+               return masqExpr
+
+       case VERDICT_REDIRECT:
+               _, _, rewriteParms, err := NewExprNAT(parms, VERDICT_REDIRECT)
+               if err != nil {
+                       log.Warning("error adding redirect verdict: %s", err)
+                       return nil
+               }
+               redirExpr := NewExprRedirect()
+               *redirExpr = append(*rewriteParms, *redirExpr...)
+               return redirExpr
+
+       case VERDICT_TPROXY:
+               _, _, rewriteParms, err := NewExprNAT(parms, VERDICT_TPROXY)
+               if err != nil {
+                       log.Warning("error adding tproxy verdict: %s", err)
+                       return nil
+               }
+               tproxyExpr := &[]expr.Any{}
+               *tproxyExpr = append(*tproxyExpr, *rewriteParms...)
+               tVerdict := NewExprTproxy()
+               *tproxyExpr = append(*tproxyExpr, *tVerdict...)
+               *tproxyExpr = append(*tproxyExpr, *NewExprAccept()...)
+               return tproxyExpr
+
+       }
+
+       // target can be empty, "ct set mark" or "log" for example
+       return &[]expr.Any{}
+}
+
+// NewExprAccept creates the accept verdict.
+func NewExprAccept() *[]expr.Any {
+       return &[]expr.Any{&expr.Verdict{
+               Kind: expr.VerdictAccept,
+       }}
+}
+
+// NewExprReject creates new Reject expression
+// icmpx, to reject the IPv4 and IPv6 traffic, icmp for ipv4, icmpv6 for ...
+// Ex.: "Target": "reject", "TargetParameters": "with tcp reset"
+// https://wiki.nftables.org/wiki-nftables/index.php/Rejecting_traffic
+func NewExprReject(parms string) *expr.Reject {
+       reject := &expr.Reject{}
+       reject.Code = unix.NFT_REJECT_ICMP_UNREACH
+       reject.Type = unix.NFT_REJECT_ICMP_UNREACH
+
+       parmList := strings.Split(parms, " ")
+       length := len(parmList)
+       if length <= 1 {
+               return reject
+       }
+       what := parmList[1]
+       how := parmList[length-1]
+
+       switch what {
+       case NFT_PROTO_TCP:
+               reject.Type = unix.NFT_REJECT_TCP_RST
+               reject.Code = unix.NFT_REJECT_TCP_RST
+       case NFT_PROTO_ICMP:
+               reject.Type = unix.NFT_REJECT_ICMP_UNREACH
+               reject.Code = GetICMPRejectCode(how)
+               return reject
+       case NFT_PROTO_ICMPX:
+               // icmp and icmpv6
+               reject.Type = unix.NFT_REJECT_ICMPX_UNREACH
+               reject.Code = GetICMPxRejectCode(how)
+               return reject
+       case NFT_PROTO_ICMPv6:
+               reject.Type = 1
+               reject.Code = GetICMPv6RejectCode(how)
+
+       default:
+       }
+
+       return reject
+}
diff --git a/daemon/firewall/nftables/exprs/verdict_test.go b/daemon/firewall/nftables/exprs/verdict_test.go
new file mode 100644 (file)
index 0000000..de34b3b
--- /dev/null
@@ -0,0 +1,318 @@
+package exprs_test
+
+import (
+       "fmt"
+       "reflect"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+type verdictTestsT struct {
+       name         string
+       verdict      string
+       parms        string
+       expectedExpr string
+       expectedKind expr.VerdictKind
+}
+
+func TestExprVerdict(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       // we must create a custom chain before using JUMP verdict.
+       tbl, _ := nftest.Fw.AddTable("yyy", exprs.NFT_FAMILY_INET)
+       nftest.Fw.Conn.AddChain(&nftables.Chain{
+               Name:  "custom-chain",
+               Table: tbl,
+       })
+       nftest.Fw.Commit()
+
+       verdictTests := []verdictTestsT{
+               {"test-accept", exprs.VERDICT_ACCEPT, "", "*expr.Verdict", expr.VerdictAccept},
+               {"test-AcCept", "AcCePt", "", "*expr.Verdict", expr.VerdictAccept},
+               {"test-ACCEPT", "ACCEPT", "", "*expr.Verdict", expr.VerdictAccept},
+               {"test-drop", exprs.VERDICT_DROP, "", "*expr.Verdict", expr.VerdictDrop},
+               //{"test-stop", exprs.VERDICT_STOP, "", "*expr.Verdict", expr.VerdictStop},
+               {"test-return", exprs.VERDICT_RETURN, "", "*expr.Verdict", expr.VerdictReturn},
+               {"test-jump", exprs.VERDICT_JUMP, "custom-chain", "*expr.Verdict", expr.VerdictJump},
+               // empty verdict must be valid at this level.
+               // it can be used with "log" or "ct set mark"
+               {"test-empty-verdict", "", "", "*expr.Verdict", expr.VerdictAccept},
+       }
+
+       for _, test := range verdictTests {
+               t.Run(test.name, func(t *testing.T) {
+                       verdExpr := exprs.NewExprVerdict(test.verdict, test.parms)
+                       r, _ := nftest.AddTestRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule with verdict expression %s", test.verdict)
+                               return
+                       }
+                       if test.name == "test-empty-verdict" {
+                               return
+                       }
+                       e := r.Exprs[0]
+                       if reflect.TypeOf(e).String() != test.expectedExpr {
+                               t.Errorf("first expression should be *expr.Verdict, instead of: %s", reflect.TypeOf(e))
+                               return
+                       }
+                       verd, ok := e.(*expr.Verdict)
+                       if !ok {
+                               t.Errorf("invalid verdict: %T", e)
+                               return
+                       }
+                       if verd.Kind != test.expectedKind {
+                               t.Errorf("invalid verdict kind: %+v, expected: %+v", verd.Kind, test.expectedKind)
+                               return
+                       }
+               })
+       }
+}
+
+func TestExprVerdictReject(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       type rejectTests struct {
+               name     string
+               parms    string
+               what     string
+               family   string
+               parmType byte
+               parmCode byte
+       }
+       tests := []rejectTests{
+               {
+                       "test-reject-tcp-RST",
+                       "with tcp reset",
+                       exprs.NFT_PROTO_TCP,
+                       exprs.NFT_FAMILY_INET,
+                       unix.NFT_REJECT_TCP_RST,
+                       unix.NFT_REJECT_TCP_RST,
+               },
+
+               {
+                       "test-reject-icmp-host-unreachable",
+                       fmt.Sprint("with icmp ", exprs.ICMP_HOST_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_HOST_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmp-addr-unreachable",
+                       fmt.Sprint("with icmp ", exprs.ICMP_ADDR_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_ADDR_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmp-prot-unreachable",
+                       fmt.Sprint("with icmp ", exprs.ICMP_PROT_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_PROT_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmp-port-unreachable",
+                       fmt.Sprint("with icmp ", exprs.ICMP_PORT_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_PORT_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmp-admin-prohibited",
+                       fmt.Sprint("with icmp ", exprs.ICMP_ADMIN_PROHIBITED),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_ADMIN_PROHIBITED),
+               },
+               {
+                       "test-reject-icmp-host-prohibited",
+                       fmt.Sprint("with icmp ", exprs.ICMP_HOST_PROHIBITED),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_HOST_PROHIBITED),
+               },
+               {
+                       "test-reject-icmp-net-prohibited",
+                       fmt.Sprint("with icmp ", exprs.ICMP_NET_PROHIBITED),
+                       exprs.NFT_FAMILY_IP,
+                       exprs.NFT_PROTO_ICMP,
+                       unix.NFT_REJECT_ICMP_UNREACH,
+                       exprs.GetICMPRejectCode(exprs.ICMP_NET_PROHIBITED),
+               },
+
+               // icmpx
+               {
+                       "test-reject-icmpx-net-unreachable",
+                       fmt.Sprint("with icmpx ", exprs.ICMP_NET_UNREACHABLE),
+                       exprs.NFT_FAMILY_INET,
+                       exprs.NFT_PROTO_ICMPX,
+                       unix.NFT_REJECT_ICMPX_UNREACH,
+                       exprs.GetICMPxRejectCode(exprs.ICMP_NET_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpx-host-unreachable",
+                       fmt.Sprint("with icmpx ", exprs.ICMP_HOST_UNREACHABLE),
+                       exprs.NFT_FAMILY_INET,
+                       exprs.NFT_PROTO_ICMPX,
+                       unix.NFT_REJECT_ICMPX_UNREACH,
+                       exprs.GetICMPxRejectCode(exprs.ICMP_HOST_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpx-prot-unreachable",
+                       fmt.Sprint("with icmpx ", exprs.ICMP_PROT_UNREACHABLE),
+                       exprs.NFT_FAMILY_INET,
+                       exprs.NFT_PROTO_ICMPX,
+                       unix.NFT_REJECT_ICMPX_UNREACH,
+                       exprs.GetICMPxRejectCode(exprs.ICMP_PROT_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpx-port-unreachable",
+                       fmt.Sprint("with icmpx ", exprs.ICMP_PORT_UNREACHABLE),
+                       exprs.NFT_FAMILY_INET,
+                       exprs.NFT_PROTO_ICMPX,
+                       unix.NFT_REJECT_ICMPX_UNREACH,
+                       exprs.GetICMPxRejectCode(exprs.ICMP_PORT_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpx-no-route",
+                       fmt.Sprint("with icmpx ", exprs.ICMP_NO_ROUTE),
+                       exprs.NFT_FAMILY_INET,
+                       exprs.NFT_PROTO_ICMPX,
+                       unix.NFT_REJECT_ICMPX_UNREACH,
+                       exprs.GetICMPxRejectCode(exprs.ICMP_NO_ROUTE),
+               },
+
+               // icmpv6
+               {
+                       "test-reject-icmpv6-net-unreachable",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_NET_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_NET_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpv6-addr-unreachable",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_ADDR_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_ADDR_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpv6-host-unreachable",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_HOST_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_HOST_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpv6-port-unreachable",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_PORT_UNREACHABLE),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_PORT_UNREACHABLE),
+               },
+               {
+                       "test-reject-icmpv6-no-route",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_NO_ROUTE),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_NO_ROUTE),
+               },
+               {
+                       "test-reject-icmpv6-reject-policy-fail",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_REJECT_POLICY_FAIL),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_REJECT_POLICY_FAIL),
+               },
+               {
+                       "test-reject-icmpv6-reject-route",
+                       fmt.Sprint("with icmpv6 ", exprs.ICMP_REJECT_ROUTE),
+                       exprs.NFT_FAMILY_IP6,
+                       exprs.NFT_PROTO_ICMPv6,
+                       1,
+                       exprs.GetICMPv6RejectCode(exprs.ICMP_REJECT_ROUTE),
+               },
+       }
+
+       for _, test := range tests {
+               t.Run(test.name, func(t *testing.T) {
+                       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_REJECT, test.parms)
+                       r, _ := nftest.AddTestRule(t, conn, verdExpr)
+                       if r == nil {
+                               t.Errorf("Error adding rule with reject verdict %s", "")
+                               return
+                       }
+                       e := r.Exprs[0]
+                       if reflect.TypeOf(e).String() != "*expr.Reject" {
+                               t.Errorf("first expression should be *expr.Verdict, instead of: %s", reflect.TypeOf(e))
+                               return
+                       }
+                       verd, ok := e.(*expr.Reject)
+                       if !ok {
+                               t.Errorf("invalid verdict: %T", e)
+                               return
+                       }
+                       //fmt.Printf("reject verd: %+v\n", verd)
+
+                       if verd.Code != uint8(test.parmCode) {
+                               t.Errorf("invalid reject verdict code: %d, expected: %d", verd.Code, test.parmCode)
+                       }
+
+               })
+       }
+}
+
+func TestExprVerdictQueue(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       verdExpr := exprs.NewExprVerdict(exprs.VERDICT_QUEUE, "num 1")
+       r, _ := nftest.AddTestRule(t, conn, verdExpr)
+       if r == nil {
+               t.Errorf("Error adding rule with Queue verdict")
+               return
+       }
+       e := r.Exprs[0]
+       if reflect.TypeOf(e).String() != "*expr.Queue" {
+               t.Errorf("first expression should be *expr.Queue, instead of: %s", reflect.TypeOf(e))
+               return
+       }
+       verd, ok := e.(*expr.Queue)
+       if !ok {
+               t.Errorf("invalid verdict: %T", e)
+               return
+       }
+       if verd.Num != 1 {
+               t.Errorf("invalid queue verdict Num: %d", verd.Num)
+       }
+
+}
diff --git a/daemon/firewall/nftables/monitor.go b/daemon/firewall/nftables/monitor.go
new file mode 100644 (file)
index 0000000..88f3d62
--- /dev/null
@@ -0,0 +1,72 @@
+package nftables
+
+import (
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+// AreRulesLoaded checks if the firewall rules for intercept traffic are loaded.
+func (n *Nft) AreRulesLoaded() bool {
+       n.Lock()
+       defer n.Unlock()
+
+       nRules := 0
+       chains, err := n.Conn.ListChains()
+       if err != nil {
+               log.Warning("[nftables] error listing nftables chains: %s", err)
+               return false
+       }
+
+       for _, c := range chains {
+               rules, err := n.Conn.GetRule(c.Table, c)
+               if err != nil {
+                       log.Warning("[nftables] Error listing rules: %s", err)
+                       continue
+               }
+               for rdx, r := range rules {
+                       if string(r.UserData) == InterceptionRuleKey {
+                               if c.Table.Name == exprs.NFT_CHAIN_FILTER && c.Name == exprs.NFT_HOOK_INPUT && rdx != 0 {
+                                       log.Warning("nftables DNS rule not in 1st position (%d)", rdx)
+                                       return false
+                               }
+                               nRules++
+                               if c.Table.Name == exprs.NFT_CHAIN_MANGLE && rdx < len(rules)-2 {
+                                       log.Warning("nfables queue rule is not the latest of the list (%d/%d), reloading", rdx, len(rules))
+                                       return false
+                               }
+                       }
+               }
+       }
+       // we expect to have exactly 3 rules (2 queue and 1 dns). If there're less or more, then we
+       // need to reload them.
+       if nRules != 3 {
+               log.Warning("nfables filter rules not loaded: %d", nRules)
+               return false
+       }
+
+       return true
+}
+
+// ReloadConfCallback gets called after the configuration changes.
+func (n *Nft) ReloadConfCallback() {
+       log.Important("reloadConfCallback changed, reloading")
+       n.DeleteSystemRules(!common.ForcedDelRules, !common.RestoreChains, log.GetLogLevel() == log.DEBUG)
+       n.AddSystemRules(common.ReloadRules, !common.BackupChains)
+}
+
+// ReloadRulesCallback gets called when the interception rules are not present.
+func (n *Nft) ReloadRulesCallback() {
+       log.Important("nftables firewall rules changed, reloading")
+       n.DisableInterception(log.GetLogLevel() == log.DEBUG)
+       time.Sleep(time.Millisecond * 500)
+       n.EnableInterception()
+}
+
+// PreloadConfCallback gets called before the fw configuration is loaded
+func (n *Nft) PreloadConfCallback() {
+       log.Info("nftables config changed, reloading")
+       n.DeleteSystemRules(!common.ForcedDelRules, common.RestoreChains, log.GetLogLevel() == log.DEBUG)
+}
diff --git a/daemon/firewall/nftables/monitor_test.go b/daemon/firewall/nftables/monitor_test.go
new file mode 100644 (file)
index 0000000..775c4bb
--- /dev/null
@@ -0,0 +1,95 @@
+package nftables_test
+
+import (
+       "testing"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       nftb "github.com/evilsocket/opensnitch/daemon/firewall/nftables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+)
+
+// mimic EnableInterception() but without NewRulesChecker()
+func addInterceptionRules(nft *nftb.Nft, t *testing.T) {
+       if err := nft.AddInterceptionTables(); err != nil {
+               t.Errorf("Error while adding interception tables: %s", err)
+               return
+       }
+       if err := nft.AddInterceptionChains(); err != nil {
+               t.Errorf("Error while adding interception chains: %s", err)
+               return
+       }
+
+       if err, _ := nft.QueueDNSResponses(common.EnableRule, common.EnableRule); err != nil {
+               t.Errorf("Error while running DNS nftables rule: %s", err)
+       }
+       if err, _ := nft.QueueConnections(common.EnableRule, common.EnableRule); err != nil {
+               t.Errorf("Error while running conntrack nftables rule: %s", err)
+       }
+}
+
+func _testMonitorReload(t *testing.T, conn *nftables.Conn, nft *nftb.Nft) {
+       tblfilter := nft.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+       if tblfilter == nil || tblfilter.Name != exprs.NFT_CHAIN_FILTER {
+               t.Error("table filter-inet not in the list")
+       }
+       chnFilterInput := nftest.Fw.GetChain(exprs.NFT_HOOK_INPUT, tblfilter, exprs.NFT_FAMILY_INET)
+       if chnFilterInput == nil {
+               t.Error("chain input-filter-inet not in the list")
+       }
+       rules, _ := conn.GetRules(tblfilter, chnFilterInput)
+       if len(rules) == 0 {
+               t.Error("DNS interception rule not added")
+       }
+       conn.FlushChain(chnFilterInput)
+       nftest.Fw.Commit()
+
+       // the rules checker checks the rules every 10s
+       reloaded := false
+       for i := 0; i < 15; i++ {
+               if r, _ := getRule(t, conn, tblfilter.Name, exprs.NFT_HOOK_INPUT, nftb.InterceptionRuleKey, 0); r != nil {
+                       reloaded = true
+                       break
+               }
+               time.Sleep(time.Second)
+       }
+       if !reloaded {
+               t.Error("rules under input-filter-inet not reloaded after 10s")
+       }
+}
+
+func TestAreRulesLoaded(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       addInterceptionRules(nftest.Fw, t)
+       if !nftest.Fw.AreRulesLoaded() {
+               t.Error("interception rules not loaded, and they should")
+       }
+
+       nftest.Fw.DelInterceptionRules()
+       if nftest.Fw.AreRulesLoaded() {
+               t.Error("interception rules are loaded, and the shouldn't")
+       }
+}
+
+func TestMonitorReload(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       nftest.Fw.EnableInterception()
+
+       // test that rules are reloaded after being deleted, but also
+       // that the monitor is not stopped after the first reload.
+       _testMonitorReload(t, conn, nftest.Fw)
+       _testMonitorReload(t, conn, nftest.Fw)
+       _testMonitorReload(t, conn, nftest.Fw)
+}
diff --git a/daemon/firewall/nftables/nftables.go b/daemon/firewall/nftables/nftables.go
new file mode 100644 (file)
index 0000000..c0b314b
--- /dev/null
@@ -0,0 +1,195 @@
+package nftables
+
+import (
+       "bytes"
+       "encoding/json"
+       "strings"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/iptables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+       "github.com/golang/protobuf/jsonpb"
+       "github.com/google/nftables"
+)
+
+// Action is the modifier we apply to a rule.
+type Action string
+
+// Actions we apply to the firewall.
+const (
+       fwKey               = "opensnitch-key"
+       InterceptionRuleKey = fwKey + "-interception"
+       SystemRuleKey       = fwKey + "-system"
+       Name                = "nftables"
+)
+
+var (
+       filterTable = &nftables.Table{
+               Family: nftables.TableFamilyINet,
+               Name:   exprs.NFT_CHAIN_FILTER,
+       }
+
+       mangleTable = &nftables.Table{
+               Family: nftables.TableFamilyINet,
+               Name:   exprs.NFT_CHAIN_FILTER,
+       }
+)
+
+// Nft holds the fields of our nftables firewall
+type Nft struct {
+       Conn   *nftables.Conn
+       chains iptables.SystemChains
+       common.Common
+       config.Config
+
+       sync.Mutex
+}
+
+// NewNft creates a new nftables object
+func NewNft() *nftables.Conn {
+       return &nftables.Conn{}
+}
+
+// Fw initializes a new nftables object
+func Fw() (*Nft, error) {
+       n := &Nft{
+               chains: iptables.SystemChains{
+                       Rules: make(map[string]*iptables.SystemRule),
+               },
+       }
+       return n, nil
+}
+
+// Name returns the name of the firewall
+func (n *Nft) Name() string {
+       return Name
+}
+
+// Init inserts the firewall rules and starts monitoring for firewall
+// changes.
+func (n *Nft) Init(qNum *int) {
+       if n.IsRunning() {
+               return
+       }
+       n.ErrChan = make(chan string, 100)
+       InitMapsStore()
+       n.SetQueueNum(qNum)
+       n.Conn = NewNft()
+
+       // In order to clean up any existing firewall rule before start,
+       // we need to load the fw configuration first to know what rules
+       // were configured.
+       n.NewSystemFwConfig(n.PreloadConfCallback, n.ReloadConfCallback)
+       n.LoadDiskConfiguration(!common.ReloadConf)
+
+       // start from a clean state
+       // The daemon may have exited unexpectedly, leaving residual fw rules, so we
+       // need to clean them up to avoid duplicated rules.
+       n.DelInterceptionRules()
+       n.AddSystemRules(!common.ReloadRules, common.BackupChains)
+       n.EnableInterception()
+
+       n.Running = true
+}
+
+// Stop deletes the firewall rules, allowing network traffic.
+func (n *Nft) Stop() {
+       if n.IsRunning() == false {
+               return
+       }
+       n.StopConfigWatcher()
+       n.StopCheckingRules()
+       n.CleanRules(log.GetLogLevel() == log.DEBUG)
+
+       n.Lock()
+       n.Running = false
+       n.Unlock()
+}
+
+// EnableInterception adds firewall rules to intercept connections
+func (n *Nft) EnableInterception() {
+       if err := n.AddInterceptionTables(); err != nil {
+               log.Error("Error while adding interception tables: %s", err)
+               return
+       }
+       if err := n.AddInterceptionChains(); err != nil {
+               log.Error("Error while adding interception chains: %s", err)
+               return
+       }
+
+       if err, _ := n.QueueDNSResponses(common.EnableRule, common.EnableRule); err != nil {
+               log.Error("Error while running DNS nftables rule: %s", err)
+       }
+       if err, _ := n.QueueConnections(common.EnableRule, common.EnableRule); err != nil {
+               log.Error("Error while running conntrack nftables rule: %s", err)
+       }
+       // start monitoring firewall rules to intercept network traffic.
+       n.NewRulesChecker(n.AreRulesLoaded, n.ReloadRulesCallback)
+}
+
+// DisableInterception removes firewall rules to intercept outbound connections.
+func (n *Nft) DisableInterception(logErrors bool) {
+       n.StopCheckingRules()
+       n.DelInterceptionRules()
+}
+
+// CleanRules deletes the rules we added.
+func (n *Nft) CleanRules(logErrors bool) {
+       n.DisableInterception(logErrors)
+       n.DeleteSystemRules(common.ForcedDelRules, common.RestoreChains, logErrors)
+}
+
+// Commit applies the queued changes, creating new objects (tables, chains, etc).
+// You add rules, chains or tables, and after calling to Flush() they're added to the system.
+// NOTE: it's very important not to call Flush() without queued tasks.
+func (n *Nft) Commit() bool {
+       if err := n.Conn.Flush(); err != nil {
+               log.Warning("%s error applying changes: %s", logTag, err)
+               return false
+       }
+       return true
+}
+
+// Serialize converts the configuration from json to protobuf
+func (n *Nft) Serialize() (*protocol.SysFirewall, error) {
+       sysfw := &protocol.SysFirewall{}
+       jun := jsonpb.Unmarshaler{
+               AllowUnknownFields: true,
+       }
+       rawConfig, err := json.Marshal(&n.SysConfig)
+       if err != nil {
+               log.Error("nftables.Serialize() struct to string error: %s", err)
+               return nil, err
+       }
+       // string to proto
+       if err := jun.Unmarshal(strings.NewReader(string(rawConfig)), sysfw); err != nil {
+               log.Error("nftables.Serialize() string to protobuf error: %s", err)
+               return nil, err
+       }
+
+       return sysfw, nil
+}
+
+// Deserialize converts a protocolbuffer structure to byte array.
+func (n *Nft) Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
+       jun := jsonpb.Marshaler{
+               OrigName:     true,
+               EmitDefaults: true,
+               Indent:       "  ",
+       }
+
+       // NOTE: '<' and '>' characters are encoded to unicode (\u003c).
+       // This has no effect on adding rules to nftables.
+       // Users can still write "<" if they want to, rules are added ok.
+
+       var b bytes.Buffer
+       if err := jun.Marshal(&b, sysfw); err != nil {
+               log.Error("nfables.Deserialize() error 2: %s", err)
+               return nil, err
+       }
+       return b.Bytes(), nil
+}
diff --git a/daemon/firewall/nftables/nftest/nftest.go b/daemon/firewall/nftables/nftest/nftest.go
new file mode 100644 (file)
index 0000000..861b30a
--- /dev/null
@@ -0,0 +1,63 @@
+package nftest
+
+import (
+       "os"
+       "runtime"
+       "testing"
+
+       nftb "github.com/evilsocket/opensnitch/daemon/firewall/nftables"
+       "github.com/google/nftables"
+       "github.com/vishvananda/netns"
+)
+
+var (
+       conn  *nftables.Conn
+       newNS netns.NsHandle
+
+       // Fw represents the nftables Fw object.
+       Fw, _ = nftb.Fw()
+)
+
+func init() {
+       nftb.InitMapsStore()
+}
+
+// SkipIfNotPrivileged will skip the test from where it's invoked,
+// to skip the test if we don't have root privileges.
+// This may occur when executing the tests on restricted environments,
+// such as containers, chroots, etc.
+func SkipIfNotPrivileged(t *testing.T) {
+       if os.Getenv("PRIVILEGED_TESTS") == "" {
+               t.Skip("Set PRIVILEGED_TESTS to 1 to launch these tests, and launch them as root, or as a user allowed to create new namespaces.")
+       }
+}
+
+// OpenSystemConn opens a new connection with the kernel in a new namespace.
+// https://github.com/google/nftables/blob/8f2d395e1089dea4966c483fbeae7e336917c095/internal/nftest/system_conn.go#L15
+func OpenSystemConn(t *testing.T) (*nftables.Conn, netns.NsHandle) {
+       t.Helper()
+       // We lock the goroutine into the current thread, as namespace operations
+       // such as those invoked by `netns.New()` are thread-local. This is undone
+       // in nftest.CleanupSystemConn().
+       runtime.LockOSThread()
+
+       ns, err := netns.New()
+       if err != nil {
+               t.Fatalf("netns.New() failed: %v", err)
+       }
+       t.Log("OpenSystemConn() with NS:", ns)
+       c, err := nftables.New(nftables.WithNetNSFd(int(ns)))
+       if err != nil {
+               t.Fatalf("nftables.New() failed: %v", err)
+       }
+       return c, ns
+}
+
+// CleanupSystemConn closes the given namespace.
+func CleanupSystemConn(t *testing.T, newNS netns.NsHandle) {
+       defer runtime.UnlockOSThread()
+
+       if err := newNS.Close(); err != nil {
+               t.Fatalf("newNS.Close() failed: %v", err)
+       }
+}
diff --git a/daemon/firewall/nftables/nftest/test_utils.go b/daemon/firewall/nftables/nftest/test_utils.go
new file mode 100644 (file)
index 0000000..10b2a21
--- /dev/null
@@ -0,0 +1,202 @@
+package nftest
+
+import (
+       "bytes"
+       "reflect"
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+)
+
+// TestsT defines the fields of a test.
+type TestsT struct {
+       Name             string
+       Family           string
+       Parms            string
+       Values           []*config.ExprValues
+       ExpectedExprsNum int
+       ExpectedExprs    []interface{}
+       ExpectedFail     bool
+}
+
+// AreExprsValid checks if the expressions defined in the given rule are valid
+// according to the expected expressions defined in the tests.
+func AreExprsValid(t *testing.T, test *TestsT, rule *nftables.Rule) bool {
+
+       total := len(rule.Exprs)
+       if total != test.ExpectedExprsNum {
+               t.Errorf("expected %d expressions, found %d", test.ExpectedExprsNum, total)
+               return false
+       }
+
+       for idx, e := range rule.Exprs {
+               if reflect.TypeOf(e).String() != reflect.TypeOf(test.ExpectedExprs[idx]).String() {
+                       t.Errorf("first expression should be %s, instead of: %s", reflect.TypeOf(test.ExpectedExprs[idx]), reflect.TypeOf(e))
+                       return false
+               }
+
+               switch e.(type) {
+               case *expr.Meta:
+                       lExpr, ok := e.(*expr.Meta)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Meta)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Meta expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.Key != lExpect.Key || lExpr.Register != lExpect.Register {
+                               t.Errorf("invalid Meta.Key,\ngot: %+v\nexpected: %+v\n", lExpr.Key, lExpect.Key)
+                       }
+                       if lExpr.SourceRegister != lExpect.SourceRegister {
+                               t.Errorf("invalid Meta.SourceRegister,\ngot: %+v\nexpected: %+v\n", lExpr.SourceRegister, lExpect.SourceRegister)
+                       }
+                       if lExpr.Register != lExpect.Register {
+                               t.Errorf("invalid Meta.Register,\ngot: %+v\nexpected: %+v\n", lExpr.SourceRegister, lExpect.SourceRegister)
+                       }
+
+               case *expr.Immediate:
+                       lExpr, ok := e.(*expr.Immediate)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Immediate)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Immediate expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if !bytes.Equal(lExpr.Data, lExpect.Data) && !test.ExpectedFail {
+                               t.Errorf("invalid Immediate.Data,\ngot: %+v,\nexpected: %+v", lExpr.Data, lExpect.Data)
+                               return false
+                       }
+
+               case *expr.TProxy:
+                       lExpr, ok := e.(*expr.TProxy)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.TProxy)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid TProxy expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.Family != lExpect.Family || lExpr.TableFamily != lExpect.TableFamily || lExpr.RegPort != lExpect.RegPort {
+                               t.Errorf("invalid TProxy expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Redir:
+                       lExpr, ok := e.(*expr.Redir)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Redir)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Redir expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.RegisterProtoMin != lExpect.RegisterProtoMin {
+                               t.Errorf("invalid Redir expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Masq:
+                       lExpr, ok := e.(*expr.Masq)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Masq)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Masq expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.ToPorts != lExpect.ToPorts ||
+                               lExpr.Random != lExpect.Random ||
+                               lExpr.FullyRandom != lExpect.FullyRandom ||
+                               lExpr.Persistent != lExpect.Persistent {
+                               t.Errorf("invalid Masq expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.NAT:
+                       lExpr, ok := e.(*expr.NAT)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.NAT)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid NAT expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.RegProtoMin != lExpect.RegProtoMin ||
+                               lExpr.RegAddrMin != lExpect.RegAddrMin ||
+                               lExpr.Random != lExpect.Random ||
+                               lExpr.FullyRandom != lExpect.FullyRandom ||
+                               lExpr.Persistent != lExpect.Persistent {
+                               t.Errorf("invalid NAT expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Quota:
+                       lExpr, ok := e.(*expr.Quota)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Quota)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Quota expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.Bytes != lExpect.Bytes ||
+                               lExpr.Over != lExpect.Over ||
+                               lExpr.Consumed != lExpect.Consumed {
+                               t.Errorf("invalid Quota.Data,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Ct:
+                       lExpr, ok := e.(*expr.Ct)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Ct)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Ct expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.Key != lExpect.Key || lExpr.Register != lExpect.Register || lExpr.SourceRegister != lExpect.SourceRegister {
+                               t.Errorf("invalid Ct parms,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Bitwise:
+                       lExpr, ok := e.(*expr.Bitwise)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Bitwise)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Bitwise expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if lExpr.Len != lExpect.Len ||
+                               !bytes.Equal(lExpr.Mask, lExpect.Mask) ||
+                               !bytes.Equal(lExpr.Xor, lExpect.Xor) ||
+                               lExpr.DestRegister != lExpect.DestRegister ||
+                               lExpr.SourceRegister != lExpect.SourceRegister {
+                               t.Errorf("invalid Bitwise parms,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Log:
+                       lExpr, ok := e.(*expr.Log)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Log)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Log expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if !bytes.Equal(lExpr.Data, lExpect.Data) && !test.ExpectedFail {
+                               t.Errorf("invalid Log.Data,\ngot: %+v,\nexpected: %+v", lExpr.Data, lExpect.Data)
+                               return false
+                       }
+                       if lExpr.Key != lExpect.Key ||
+                               lExpr.Level != lExpect.Level ||
+                               lExpr.Group != lExpect.Group ||
+                               lExpr.Snaplen != lExpect.Snaplen ||
+                               lExpr.QThreshold != lExpect.QThreshold {
+                               t.Errorf("invalid Log fields,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+
+               case *expr.Cmp:
+                       lExpr, ok := e.(*expr.Cmp)
+                       lExpect, okExpected := test.ExpectedExprs[idx].(*expr.Cmp)
+                       if !ok || !okExpected {
+                               t.Errorf("invalid Cmp expr,\ngot: %+v,\nexpected: %+v", lExpr, lExpect)
+                               return false
+                       }
+                       if !bytes.Equal(lExpr.Data, lExpect.Data) && !test.ExpectedFail {
+                               t.Errorf("invalid Cmp.Data,\ngot: %+v,\nexpected: %+v", lExpr.Data, lExpect.Data)
+                               return false
+                       }
+               }
+       }
+
+       return true
+}
diff --git a/daemon/firewall/nftables/nftest/utils.go b/daemon/firewall/nftables/nftest/utils.go
new file mode 100644 (file)
index 0000000..7d05138
--- /dev/null
@@ -0,0 +1,117 @@
+package nftest
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+)
+
+// AddTestRule adds a generic table, chain and rule with the given expression.
+func AddTestRule(t *testing.T, conn *nftables.Conn, exp *[]expr.Any) (*nftables.Rule, *nftables.Chain) {
+
+       _, err := Fw.AddTable("yyy", exprs.NFT_FAMILY_INET)
+       if err != nil {
+               t.Errorf("pre step add_table() yyy-inet failed: %s", err)
+               return nil, nil
+       }
+       chn := Fw.AddChain(
+               exprs.NFT_HOOK_INPUT,
+               "yyy",
+               exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityFilter,
+               nftables.ChainTypeFilter,
+               nftables.ChainHookInput,
+               nftables.ChainPolicyAccept)
+       if chn == nil {
+               t.Error("pre step add_chain() input-yyy-inet failed")
+               return nil, nil
+       }
+       //nft.Commit()
+
+       r, err := Fw.AddRule(
+               exprs.NFT_HOOK_INPUT, "yyy", exprs.NFT_FAMILY_INET,
+               0,
+               "key-yyy",
+               exp)
+       if err != nil {
+               t.Errorf("Error adding rule: %s", err)
+               return nil, nil
+       }
+       t.Logf("Rule: %+v", r)
+
+       return r, chn
+}
+
+// AddTestSNATRule adds a generic table, chain and rule with the given expression.
+func AddTestSNATRule(t *testing.T, conn *nftables.Conn, exp *[]expr.Any) (*nftables.Rule, *nftables.Chain) {
+
+       _, err := Fw.AddTable("uuu", exprs.NFT_FAMILY_INET)
+       if err != nil {
+               t.Errorf("pre step add_table() uuu-inet failed: %s", err)
+               return nil, nil
+       }
+       chn := Fw.AddChain(
+               exprs.NFT_HOOK_POSTROUTING,
+               "uuu",
+               exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityNATSource,
+               nftables.ChainTypeNAT,
+               nftables.ChainHookPostrouting,
+               nftables.ChainPolicyAccept)
+       if chn == nil {
+               t.Error("pre step add_chain() input-uuu-inet failed")
+               return nil, nil
+       }
+       //nft.Commit()
+
+       r, err := Fw.AddRule(
+               exprs.NFT_HOOK_POSTROUTING, "uuu", exprs.NFT_FAMILY_INET,
+               0,
+               "key-uuu",
+               exp)
+       if err != nil {
+               t.Errorf("Error adding rule: %s", err)
+               return nil, nil
+       }
+       t.Logf("Rule: %+v", r)
+
+       return r, chn
+}
+
+// AddTestDNATRule adds a generic table, chain and rule with the given expression.
+func AddTestDNATRule(t *testing.T, conn *nftables.Conn, exp *[]expr.Any) (*nftables.Rule, *nftables.Chain) {
+
+       _, err := Fw.AddTable("iii", exprs.NFT_FAMILY_INET)
+       if err != nil {
+               t.Errorf("pre step add_table() iii-inet failed: %s", err)
+               return nil, nil
+       }
+       chn := Fw.AddChain(
+               exprs.NFT_HOOK_PREROUTING,
+               "iii",
+               exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityNATDest,
+               nftables.ChainTypeNAT,
+               nftables.ChainHookPrerouting,
+               nftables.ChainPolicyAccept)
+       if chn == nil {
+               t.Error("pre step add_chain() input-iii-inet failed")
+               return nil, nil
+       }
+       //nft.Commit()
+
+       r, err := Fw.AddRule(
+               exprs.NFT_HOOK_PREROUTING, "iii", exprs.NFT_FAMILY_INET,
+               0,
+               "key-iii",
+               exp)
+       if err != nil {
+               t.Errorf("Error adding rule: %s", err)
+               return nil, nil
+       }
+       t.Logf("Rule: %+v", r)
+
+       return r, chn
+}
diff --git a/daemon/firewall/nftables/parser.go b/daemon/firewall/nftables/parser.go
new file mode 100644 (file)
index 0000000..2605c89
--- /dev/null
@@ -0,0 +1,195 @@
+package nftables
+
+import (
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+)
+
+// nftables rules are composed of expressions, for example:
+// tcp dport 443 ip daddr 192.168.1.1
+// \-----------/ \------------------/
+// with these format:
+// keyword1<SPACE>keyword2<SPACE>value...
+//
+// here we parse the expression, and based on keyword1, we build the rule with the given options.
+//
+// If the rule has multiple values (tcp dport 80,443,8080), no spaces are allowed,
+// and the separator is a ",", instead of the format { 80, 443, 8080 }
+//
+// In order to debug invalid expressions, or how to build new ones, use the following command:
+// # nft --debug netlink  add rule filter output mark set 1
+// ip filter output
+//  [ immediate reg 1 0x00000001 ]
+//  [ meta set mark with reg 1 ]
+//
+// Debugging added rules:
+// nft --debug netlink list ruleset
+//
+// https://wiki.archlinux.org/title/Nftables#Expressions
+// https://wiki.nftables.org/wiki-nftables/index.php/Building_rules_through_expressions
+func (n *Nft) parseExpression(table, chain, family string, expression *config.Expressions) *[]expr.Any {
+       var exprList []expr.Any
+       cmpOp := exprs.NewOperator(expression.Statement.Op)
+
+       switch expression.Statement.Name {
+
+       case exprs.NFT_CT:
+               exprCt := n.buildConntrackRule(expression.Statement.Values, &cmpOp)
+               if exprCt == nil {
+                       log.Warning("%s Ct statement error", logTag)
+                       return nil
+               }
+               exprList = append(exprList, *exprCt...)
+
+       case exprs.NFT_META:
+               metaExpr, err := exprs.NewExprMeta(expression.Statement.Values, &cmpOp)
+               if err != nil {
+                       log.Warning("%s meta statement error: %s", logTag, err)
+                       return nil
+               }
+
+               for _, exprValue := range expression.Statement.Values {
+                       switch exprValue.Key {
+                       case exprs.NFT_META_L4PROTO:
+                               l4rule, err := n.buildL4ProtoRule(table, family, exprValue.Value, &cmpOp)
+                               if err != nil {
+                                       log.Warning("%s meta.l4proto statement error: %s", logTag, err)
+                                       return nil
+                               }
+                               *metaExpr = append(*metaExpr, *l4rule...)
+                       case exprs.NFT_DPORT, exprs.NFT_SPORT:
+                               exprPDir, err := exprs.NewExprPortDirection(exprValue.Key)
+                               if err != nil {
+                                       log.Warning("%s ports statement error: %s", logTag, err)
+                                       return nil
+                               }
+                               *metaExpr = append(*metaExpr, []expr.Any{exprPDir}...)
+                               portsRule, err := n.buildPortsRule(table, family, exprValue.Value, &cmpOp)
+                               if err != nil {
+                                       log.Warning("%s meta.l4proto.ports statement error: %s", logTag, err)
+                                       return nil
+                               }
+                               *metaExpr = append(*metaExpr, *portsRule...)
+                       }
+               }
+               return metaExpr
+
+       case exprs.NFT_ETHER:
+               etherExpr, err := exprs.NewExprEther(expression.Statement.Values)
+               if err != nil {
+                       log.Warning("%s ether statement error: %s", logTag, err)
+                       return nil
+               }
+               return etherExpr
+
+       // TODO: support iif, oif
+       case exprs.NFT_IIFNAME, exprs.NFT_OIFNAME:
+               isOut := expression.Statement.Name == exprs.NFT_OIFNAME
+               iface := expression.Statement.Values[0].Key
+               if iface == "" {
+                       log.Warning("%s network interface statement error: %s", logTag, expression.Statement.Name)
+                       return nil
+               }
+               exprList = append(exprList, *exprs.NewExprIface(iface, isOut, cmpOp)...)
+
+       case exprs.NFT_FAMILY_IP, exprs.NFT_FAMILY_IP6:
+               exprIP, err := exprs.NewExprIP(family, expression.Statement.Values, cmpOp)
+               if err != nil {
+                       log.Warning("%s addr statement error: %s", logTag, err)
+                       return nil
+               }
+               exprList = append(exprList, *exprIP...)
+
+       case exprs.NFT_PROTO_ICMP, exprs.NFT_PROTO_ICMPv6:
+               exprICMP := n.buildICMPRule(table, family, expression.Statement.Name, expression.Statement.Values)
+               if exprICMP == nil {
+                       log.Warning("%s icmp statement error", logTag)
+                       return nil
+               }
+               exprList = append(exprList, *exprICMP...)
+
+       case exprs.NFT_LOG:
+               exprLog, err := exprs.NewExprLog(expression.Statement)
+               if err != nil {
+                       log.Warning("%s log statement error", logTag)
+                       return nil
+               }
+               exprList = append(exprList, *exprLog...)
+
+       case exprs.NFT_LIMIT:
+               exprLimit, err := exprs.NewExprLimit(expression.Statement)
+               if err != nil {
+                       log.Warning("%s %s", logTag, err)
+                       return nil
+               }
+               exprList = append(exprList, *exprLimit...)
+
+       case exprs.NFT_PROTO_UDP, exprs.NFT_PROTO_TCP, exprs.NFT_PROTO_UDPLITE, exprs.NFT_PROTO_SCTP, exprs.NFT_PROTO_DCCP:
+               exprProto, err := exprs.NewExprProtocol(expression.Statement.Name)
+               if err != nil {
+                       log.Warning("%s proto statement error: %s", logTag, err)
+                       return nil
+               }
+               exprList = append(exprList, *exprProto...)
+
+               for _, exprValue := range expression.Statement.Values {
+
+                       switch exprValue.Key {
+                       case exprs.NFT_DPORT, exprs.NFT_SPORT:
+                               exprPDir, err := exprs.NewExprPortDirection(exprValue.Key)
+                               if err != nil {
+                                       log.Warning("%s ports statement error: %s", logTag, err)
+                                       return nil
+                               }
+                               exprList = append(exprList, []expr.Any{exprPDir}...)
+                               portsRule, err := n.buildPortsRule(table, family, exprValue.Value, &cmpOp)
+                               if err != nil {
+                                       log.Warning("%s proto.ports statement error: %s", logTag, err)
+                                       return nil
+                               }
+                               exprList = append(exprList, *portsRule...)
+                       }
+
+               }
+
+       case exprs.NFT_QUOTA:
+               exprQuota, err := exprs.NewQuota(expression.Statement.Values)
+               if err != nil {
+                       log.Warning("%s quota statement error: %s", logTag, err)
+                       return nil
+               }
+
+               exprList = append(exprList, *exprQuota...)
+
+       case exprs.NFT_NOTRACK:
+               exprList = append(exprList, *exprs.NewNoTrack()...)
+
+       case exprs.NFT_COUNTER:
+               defaultCounterName := "opensnitch"
+               counterObj := &nftables.CounterObj{
+                       Table:   &nftables.Table{Name: table, Family: nftables.TableFamilyIPv4},
+                       Name:    defaultCounterName,
+                       Bytes:   0,
+                       Packets: 0,
+               }
+               for _, counterOption := range expression.Statement.Values {
+                       switch counterOption.Key {
+                       case exprs.NFT_COUNTER_NAME:
+                               defaultCounterName = counterOption.Value
+                               counterObj.Name = defaultCounterName
+                       case exprs.NFT_COUNTER_BYTES:
+                               // TODO: allow to set initial bytes/packets?
+                               counterObj.Bytes = 1
+                       case exprs.NFT_COUNTER_PACKETS:
+                               counterObj.Packets = 1
+                       }
+               }
+               n.Conn.AddObj(counterObj)
+               exprList = append(exprList, *exprs.NewExprCounter(defaultCounterName)...)
+       }
+
+       return &exprList
+}
diff --git a/daemon/firewall/nftables/rule_helpers.go b/daemon/firewall/nftables/rule_helpers.go
new file mode 100644 (file)
index 0000000..158b0f6
--- /dev/null
@@ -0,0 +1,228 @@
+package nftables
+
+import (
+       "fmt"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+)
+
+// rules examples: https://github.com/google/nftables/blob/master/nftables_test.go
+
+func (n *Nft) buildICMPRule(table, family string, icmpProtoVersion string, icmpOptions []*config.ExprValues) *[]expr.Any {
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               return nil
+       }
+       offset := uint32(0)
+       icmpType := uint8(0)
+       setType := nftables.SetDatatype{}
+
+       switch icmpProtoVersion {
+       case exprs.NFT_PROTO_ICMP:
+               setType = nftables.TypeICMPType
+       case exprs.NFT_PROTO_ICMPv6:
+               setType = nftables.TypeICMP6Type
+       default:
+               return nil
+       }
+
+       exprICMP, _ := exprs.NewExprProtocol(icmpProtoVersion)
+       ICMPrule := []expr.Any{}
+       ICMPrule = append(ICMPrule, *exprICMP...)
+
+       ICMPtemp := []expr.Any{}
+       setElements := []nftables.SetElement{}
+       for _, icmp := range icmpOptions {
+               switch icmp.Key {
+               case exprs.NFT_ICMP_TYPE:
+                       icmpTypeList := strings.Split(icmp.Value, ",")
+                       for _, icmpTypeStr := range icmpTypeList {
+                               if exprs.NFT_PROTO_ICMPv6 == icmpProtoVersion {
+                                       icmpType = exprs.GetICMPv6Type(icmpTypeStr)
+                               } else {
+                                       icmpType = exprs.GetICMPType(icmpTypeStr)
+                               }
+                               exprCmp := &expr.Cmp{
+                                       Op:       expr.CmpOpEq,
+                                       Register: 1,
+                                       Data:     []byte{icmpType},
+                               }
+                               ICMPtemp = append(ICMPtemp, []expr.Any{exprCmp}...)
+
+                               // fill setElements. If there're more than 1 icmp type we'll use it later
+                               setElements = append(setElements,
+                                       []nftables.SetElement{
+                                               {
+                                                       Key: []byte{icmpType},
+                                               },
+                                       }...)
+                       }
+               case exprs.NFT_ICMP_CODE:
+                       // TODO
+                       offset = 1
+               }
+       }
+
+       ICMPrule = append(ICMPrule, []expr.Any{
+               &expr.Payload{
+                       DestRegister: 1,
+                       Base:         expr.PayloadBaseTransportHeader,
+                       Offset:       offset, // 0 type, 1 code
+                       Len:          1,
+               },
+       }...)
+
+       if len(setElements) == 1 {
+               ICMPrule = append(ICMPrule, ICMPtemp...)
+       } else {
+               set := &nftables.Set{
+                       Anonymous: true,
+                       Constant:  true,
+                       Table:     tbl,
+                       KeyType:   setType,
+               }
+               if err := n.Conn.AddSet(set, setElements); err != nil {
+                       log.Warning("%s AddSet() error: %s", logTag, err)
+                       return nil
+               }
+               sysSets = append(sysSets, []*nftables.Set{set}...)
+
+               ICMPrule = append(ICMPrule, []expr.Any{
+                       &expr.Lookup{
+                               SourceRegister: 1,
+                               SetName:        set.Name,
+                               SetID:          set.ID,
+                       }}...)
+       }
+
+       return &ICMPrule
+}
+
+func (n *Nft) buildConntrackRule(ctOptions []*config.ExprValues, cmpOp *expr.CmpOp) *[]expr.Any {
+       exprList := []expr.Any{}
+
+       setMark := false
+       for _, ctOption := range ctOptions {
+               switch ctOption.Key {
+               // we expect to have multiple "state" keys:
+               // { "state": "established", "state": "related" }
+               case exprs.NFT_CT_STATE:
+                       ctExprState, err := exprs.NewExprCtState(ctOptions)
+                       if err != nil {
+                               log.Warning("%s ct set state error: %s", logTag, err)
+                               return nil
+                       }
+                       exprList = append(exprList, *ctExprState...)
+                       exprList = append(exprList,
+                               &expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
+                       )
+                       // we only need to iterate once here
+                       goto Exit
+               case exprs.NFT_CT_SET_MARK:
+                       setMark = true
+               case exprs.NFT_CT_MARK:
+                       ctExprMark, err := exprs.NewExprCtMark(setMark, ctOption.Value, cmpOp)
+                       if err != nil {
+                               log.Warning("%s ct mark error: %s", logTag, err)
+                               return nil
+                       }
+                       exprList = append(exprList, *ctExprMark...)
+                       goto Exit
+               default:
+                       log.Warning("%s invalid conntrack option: %s", logTag, ctOption)
+                       return nil
+               }
+       }
+
+Exit:
+       return &exprList
+}
+
+// buildL4ProtoRule helper builds a new protocol rule to match ports and protocols.
+//
+// nft --debug=netlink add rule filter input meta l4proto { tcp, udp }  th dport 53
+//     __set%d filter 3 size 2
+//     __set%d filter 0
+//             element 00000006  : 0 [end]     element 00000011  : 0 [end]
+//     ip filter input
+//       [ meta load l4proto => reg 1 ]
+//       [ lookup reg 1 set __set%d ]
+//       [ payload load 2b @ transport header + 2 => reg 1 ]
+//       [ cmp eq reg 1 0x00003500 ]
+func (n *Nft) buildL4ProtoRule(table, family, l4prots string, cmpOp *expr.CmpOp) (*[]expr.Any, error) {
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               return nil, fmt.Errorf("Invalid table (%s, %s)", table, family)
+       }
+       exprList := []expr.Any{}
+       if strings.Index(l4prots, ",") != -1 {
+               set := &nftables.Set{
+                       Anonymous: true,
+                       Constant:  true,
+                       Table:     tbl,
+                       KeyType:   nftables.TypeInetProto,
+               }
+               protoSet := exprs.NewExprProtoSet(l4prots)
+               if err := n.Conn.AddSet(set, *protoSet); err != nil {
+                       log.Warning("%s protoSet, AddSet() error: %s", logTag, err)
+                       return nil, err
+               }
+               exprList = append(exprList, &expr.Lookup{
+                       SourceRegister: 1,
+                       SetName:        set.Name,
+                       SetID:          set.ID,
+               })
+       } else {
+               exprProto := exprs.NewExprL4Proto(l4prots, cmpOp)
+               exprList = append(exprList, *exprProto...)
+       }
+
+       return &exprList, nil
+}
+
+func (n *Nft) buildPortsRule(table, family, ports string, cmpOp *expr.CmpOp) (*[]expr.Any, error) {
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               return nil, fmt.Errorf("Invalid table (%s, %s)", table, family)
+       }
+       exprList := []expr.Any{}
+       if strings.Index(ports, ",") != -1 {
+               set := &nftables.Set{
+                       Anonymous: true,
+                       Constant:  true,
+                       Table:     tbl,
+                       KeyType:   nftables.TypeInetService,
+               }
+               setElements := exprs.NewExprPortSet(ports)
+               if err := n.Conn.AddSet(set, *setElements); err != nil {
+                       log.Warning("%s portSet, AddSet() error: %s", logTag, err)
+                       return nil, err
+               }
+               exprList = append(exprList, &expr.Lookup{
+                       SourceRegister: 1,
+                       SetName:        set.Name,
+                       SetID:          set.ID,
+               })
+               sysSets = append(sysSets, []*nftables.Set{set}...)
+       } else if strings.Index(ports, "-") != -1 {
+               portRange, err := exprs.NewExprPortRange(ports, cmpOp)
+               if err != nil {
+                       log.Warning("%s invalid portRange: %s, %s", logTag, ports, err)
+                       return nil, err
+               }
+               exprList = append(exprList, *portRange...)
+       } else {
+               exprPort, err := exprs.NewExprPort(ports, cmpOp)
+               if err != nil {
+                       return nil, err
+               }
+               exprList = append(exprList, *exprPort...)
+       }
+
+       return &exprList, nil
+}
diff --git a/daemon/firewall/nftables/rules.go b/daemon/firewall/nftables/rules.go
new file mode 100644 (file)
index 0000000..fdd79d9
--- /dev/null
@@ -0,0 +1,283 @@
+package nftables
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+       "github.com/google/nftables/binaryutil"
+       "github.com/google/nftables/expr"
+       "golang.org/x/sys/unix"
+)
+
+// QueueDNSResponses redirects DNS responses to us, in order to keep a cache
+// of resolved domains.
+// This rule must be added in top of the system rules, otherwise it may get bypassed.
+// nft insert rule ip filter input udp sport 53 queue num 0 bypass
+func (n *Nft) QueueDNSResponses(enable bool, logError bool) (error, error) {
+       if n.Conn == nil {
+               return nil, nil
+       }
+       families := []string{exprs.NFT_FAMILY_INET}
+       for _, fam := range families {
+               table := n.GetTable(exprs.NFT_CHAIN_FILTER, fam)
+               chain := GetChain(exprs.NFT_HOOK_INPUT, table)
+               if table == nil {
+                       log.Error("QueueDNSResponses() Error getting table: %s-filter", fam)
+                       continue
+               }
+               if chain == nil {
+                       log.Error("QueueDNSResponses() Error getting chain: %s-%d", table.Name, table.Family)
+                       continue
+               }
+
+               // nft list ruleset -a
+               n.Conn.InsertRule(&nftables.Rule{
+                       Position: 0,
+                       Table:    table,
+                       Chain:    chain,
+                       Exprs: []expr.Any{
+                               &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
+                               &expr.Cmp{
+                                       Op:       expr.CmpOpEq,
+                                       Register: 1,
+                                       Data:     []byte{unix.IPPROTO_UDP},
+                               },
+                               &expr.Payload{
+                                       DestRegister: 1,
+                                       Base:         expr.PayloadBaseTransportHeader,
+                                       Offset:       0,
+                                       Len:          2,
+                               },
+                               &expr.Cmp{
+                                       Op:       expr.CmpOpEq,
+                                       Register: 1,
+                                       Data:     binaryutil.BigEndian.PutUint16(uint16(53)),
+                               },
+                               &expr.Queue{
+                                       Num:  n.QueueNum,
+                                       Flag: expr.QueueFlagBypass,
+                               },
+                       },
+                       // rule key, to allow get it later by key
+                       UserData: []byte(InterceptionRuleKey),
+               })
+       }
+       // apply changes
+       if !n.Commit() {
+               return fmt.Errorf("Error adding DNS interception rules"), nil
+       }
+
+       return nil, nil
+}
+
+// QueueConnections inserts the firewall rule which redirects connections to us.
+// Connections are queued until the user denies/accept them, or reaches a timeout.
+// This rule must be added at the end of all the other rules, that way we can add
+// rules above this one to exclude a service/app from being intercepted.
+// nft insert rule ip mangle OUTPUT ct state new queue num 0 bypass
+func (n *Nft) QueueConnections(enable bool, logError bool) (error, error) {
+       if n.Conn == nil {
+               return nil, fmt.Errorf("nftables QueueConnections: netlink connection not active")
+       }
+       table := n.GetTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
+       if table == nil {
+               return nil, fmt.Errorf("QueueConnections() Error getting table mangle-inet")
+       }
+       chain := GetChain(exprs.NFT_HOOK_OUTPUT, table)
+       if chain == nil {
+               return nil, fmt.Errorf("QueueConnections() Error getting outputChain: output-%s", table.Name)
+       }
+
+       n.Conn.AddRule(&nftables.Rule{
+               Position: 0,
+               Table:    table,
+               Chain:    chain,
+               Exprs: []expr.Any{
+                       &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
+                       &expr.Cmp{
+                               Op:       expr.CmpOpNeq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_TCP},
+                       },
+                       &expr.Ct{Register: 1, SourceRegister: false, Key: expr.CtKeySTATE},
+                       &expr.Bitwise{
+                               SourceRegister: 1,
+                               DestRegister:   1,
+                               Len:            4,
+                               Mask:           binaryutil.NativeEndian.PutUint32(expr.CtStateBitNEW | expr.CtStateBitRELATED),
+                               Xor:            binaryutil.NativeEndian.PutUint32(0),
+                       },
+                       &expr.Cmp{Op: expr.CmpOpNeq, Register: 1, Data: []byte{0, 0, 0, 0}},
+                       &expr.Queue{
+                               Num:  n.QueueNum,
+                               Flag: expr.QueueFlagBypass,
+                       },
+               },
+               // rule key, to allow get it later by key
+               UserData: []byte(InterceptionRuleKey),
+       })
+
+       /* nft --debug=netlink add rule inet mangle output tcp flags '& (fin|syn|rst|ack) == syn' queue bypass num 0
+       [ meta load l4proto => reg 1 ]
+       [ cmp eq reg 1 0x00000006 ]
+       [ payload load 1b @ transport header + 13 => reg 1 ]
+       [ bitwise reg 1 = ( reg 1 & 0x00000002 ) ^ 0x00000000 ]
+       [ cmp neq reg 1 0x00000000 ]
+       [ queue num 0 bypass ]
+
+       Intercept packets *only* with the SYN flag set.
+       Using 'ct state NEW' causes to intercept packets with other flags set, which
+       sometimes means that we receive outbound connections not in the expected order:
+         443:1.1.1.1 -> 192.168.123:12345 (bits ACK, ACK+PSH or SYN+ACK set)
+       */
+       n.Conn.AddRule(&nftables.Rule{
+               Position: 0,
+               Table:    table,
+               Chain:    chain,
+               Exprs: []expr.Any{
+                       &expr.Meta{Key: expr.MetaKeyL4PROTO, Register: 1},
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{unix.IPPROTO_TCP},
+                       },
+                       &expr.Payload{
+                               DestRegister: 1,
+                               Base:         expr.PayloadBaseTransportHeader,
+                               Offset:       13,
+                               Len:          1,
+                       },
+                       &expr.Bitwise{
+                               DestRegister:   1,
+                               SourceRegister: 1,
+                               Len:            1,
+                               Mask:           []byte{0x17},
+                               Xor:            []byte{0x00},
+                       },
+                       &expr.Cmp{
+                               Op:       expr.CmpOpEq,
+                               Register: 1,
+                               Data:     []byte{0x02},
+                       },
+                       &expr.Queue{
+                               Num:  n.QueueNum,
+                               Flag: expr.QueueFlagBypass,
+                       },
+               },
+               // rule key, to allow get it later by key
+               UserData: []byte(InterceptionRuleKey),
+       })
+
+       // apply changes
+       if !n.Commit() {
+               return fmt.Errorf("Error adding interception rule "), nil
+       }
+
+       return nil, nil
+}
+
+// InsertRule inserts a rule at the top of rules list.
+func (n *Nft) InsertRule(chain, table, family string, position uint64, exprs *[]expr.Any) error {
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               return fmt.Errorf("%s getting table: %s, %s", logTag, table, family)
+       }
+
+       chainKey := getChainKey(chain, tbl)
+       chn, chok := sysChains.Load(chainKey)
+       if !chok {
+               return fmt.Errorf("%s getting table: %s, %s", logTag, table, family)
+       }
+
+       rule := &nftables.Rule{
+               Position: position,
+               Table:    tbl,
+               Chain:    chn.(*nftables.Chain),
+               Exprs:    *exprs,
+               UserData: []byte(SystemRuleKey),
+       }
+       n.Conn.InsertRule(rule)
+       if !n.Commit() {
+               return fmt.Errorf("rule not added")
+       }
+
+       return nil
+}
+
+// AddRule adds a rule to the system.
+func (n *Nft) AddRule(chain, table, family string, position uint64, key string, exprs *[]expr.Any) (*nftables.Rule, error) {
+       tbl := n.GetTable(table, family)
+       if tbl == nil {
+               return nil, fmt.Errorf("getting %s table: %s, %s", logTag, table, family)
+       }
+
+       chainKey := getChainKey(chain, tbl)
+       chn, chok := sysChains.Load(chainKey)
+       if !chok {
+               return nil, fmt.Errorf("getting table: %s, %s", table, family)
+       }
+
+       rule := &nftables.Rule{
+               Position: position,
+               Table:    tbl,
+               Chain:    chn.(*nftables.Chain),
+               Exprs:    *exprs,
+               UserData: []byte(key),
+       }
+       n.Conn.AddRule(rule)
+       if !n.Commit() {
+               return nil, fmt.Errorf("adding %s rule", logTag)
+       }
+
+       return rule, nil
+}
+
+func (n *Nft) delRulesByKey(key string) error {
+       chains, err := n.Conn.ListChains()
+       if err != nil {
+               return fmt.Errorf("error listing nftables chains (%s): %s", key, err)
+       }
+       for _, c := range chains {
+               rules, err := n.Conn.GetRule(c.Table, c)
+               if err != nil {
+                       log.Warning("Error listing rules (%s): %s", key, err)
+                       continue
+               }
+               delRules := 0
+               for _, r := range rules {
+                       if string(r.UserData) != key {
+                               continue
+                       }
+                       // just passing the r object doesn't work.
+                       if err := n.Conn.DelRule(&nftables.Rule{
+                               Table:  c.Table,
+                               Chain:  c,
+                               Handle: r.Handle,
+                       }); err != nil {
+                               log.Warning("[nftables] error deleting rule (%s): %s", key, err)
+                               continue
+                       }
+                       delRules++
+               }
+               if delRules > 0 {
+                       if !n.Commit() {
+                               log.Warning("%s error deleting rules: %s", logTag, err)
+                       }
+               }
+               if len(rules) == 0 || len(rules) == delRules {
+                       _, chfound := sysChains.Load(getChainKey(c.Name, c.Table))
+                       if chfound {
+                               n.DelChain(c)
+                       }
+               }
+       }
+
+       return nil
+}
+
+// DelInterceptionRules deletes our interception rules, by key.
+func (n *Nft) DelInterceptionRules() {
+       n.delRulesByKey(InterceptionRuleKey)
+}
diff --git a/daemon/firewall/nftables/rules_test.go b/daemon/firewall/nftables/rules_test.go
new file mode 100644 (file)
index 0000000..e089909
--- /dev/null
@@ -0,0 +1,215 @@
+package nftables_test
+
+import (
+       "testing"
+
+       nftb "github.com/evilsocket/opensnitch/daemon/firewall/nftables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+)
+
+func getRulesList(t *testing.T, conn *nftables.Conn, family, tblName, chnName string) ([]*nftables.Rule, int) {
+       chains, err := conn.ListChains()
+       if err != nil {
+               return nil, -1
+       }
+
+       for rdx, c := range chains {
+               if c.Table.Family == nftb.GetFamilyCode(family) && c.Table.Name == tblName && c.Name == chnName {
+                       rules, err := conn.GetRule(c.Table, c)
+                       if err != nil {
+                               return nil, -1
+                       }
+                       return rules, rdx
+               }
+       }
+
+       return nil, -1
+}
+
+func getRule(t *testing.T, conn *nftables.Conn, tblName, chnName, key string, ruleHandle uint64) (*nftables.Rule, int) {
+       chains, err := conn.ListChains()
+       if err != nil {
+               return nil, -1
+       }
+
+       for _, c := range chains {
+               rules, err := conn.GetRule(c.Table, c)
+               if err != nil {
+                       continue
+               }
+               for rdx, r := range rules {
+                       //t.Logf("Table: %s<->%s, Chain: %s<->%s, Rule Handle: %d<->%d, UserData: %s<->%s", c.Table.Name, tblName, c.Name, chnName, r.Handle, ruleHandle, string(r.UserData), key)
+                       if c.Table.Name == tblName && c.Name == chnName {
+                               if ruleHandle > 0 && r.Handle == ruleHandle {
+                                       return r, rdx
+                               }
+                               if key != "" && string(r.UserData) == key {
+                                       return r, rdx
+                               }
+                       }
+               }
+       }
+
+       return nil, -1
+}
+
+func TestAddRule(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       r, chn := nftest.AddTestRule(t, conn, exprs.NewNoTrack())
+
+       /*
+               _, err := nft.AddTable("yyy", exprs.NFT_FAMILY_INET)
+               if err != nil {
+                       t.Error("pre step add_table() yyy-inet failed")
+               }
+               chn := nft.AddChain(
+                       exprs.NFT_HOOK_INPUT,
+                       "yyy",
+                       exprs.NFT_FAMILY_INET,
+                       nftables.ChainPriorityFilter,
+                       nftables.ChainTypeFilter,
+                       nftables.ChainHookInput,
+                       nftables.ChainPolicyAccept)
+               if chn == nil {
+                       t.Error("pre step add_chain() input-yyy-inet failed")
+               }
+
+               r, err := nft.addRule(
+                       exprs.NFT_HOOK_INPUT, "yyy", exprs.NFT_FAMILY_INET,
+                       0,
+                       "key-yyy",
+                       exprs.NewNoTrack())
+               if err != nil {
+                       t.Errorf("Error adding rule: %s", err)
+               }
+       */
+
+       rules, err := conn.GetRules(chn.Table, chn)
+       if err != nil || len(rules) != 1 {
+               t.Errorf("Rule not added, total: %d", len(rules))
+       }
+       t.Log(r.Handle)
+}
+
+func TestInsertRule(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       _, err := nftest.Fw.AddTable("yyy", exprs.NFT_FAMILY_INET)
+       if err != nil {
+               t.Error("pre step add_table() yyy-inet failed")
+       }
+       chn := nftest.Fw.AddChain(
+               exprs.NFT_HOOK_INPUT,
+               "yyy",
+               exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityFilter,
+               nftables.ChainTypeFilter,
+               nftables.ChainHookInput,
+               nftables.ChainPolicyAccept)
+       if chn == nil {
+               t.Error("pre step add_chain() input-yyy-inet failed")
+       }
+
+       err = nftest.Fw.InsertRule(
+               exprs.NFT_HOOK_INPUT, "yyy", exprs.NFT_FAMILY_INET,
+               0,
+               exprs.NewNoTrack())
+       if err != nil {
+               t.Errorf("Error inserting rule: %s", err)
+       }
+       rules, err := conn.GetRules(chn.Table, chn)
+       if err != nil || len(rules) != 1 {
+               t.Errorf("Rule not inserted, total: %d", len(rules))
+       }
+}
+
+func TestQueueConnections(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       _, err := nftest.Fw.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
+       if err != nil {
+               t.Error("pre step add_table() mangle-inet failed")
+       }
+       chn := nftest.Fw.AddChain(
+               exprs.NFT_HOOK_OUTPUT, exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityFilter,
+               nftables.ChainTypeFilter,
+               nftables.ChainHookInput,
+               nftables.ChainPolicyAccept)
+       if chn == nil {
+               t.Error("pre step add_chain() output-mangle-inet failed")
+       }
+
+       if err1, err2 := nftest.Fw.QueueConnections(true, true); err1 != nil && err2 != nil {
+               t.Errorf("rule to queue connections not added: %s, %s", err1, err2)
+       }
+
+       r, _ := getRule(t, conn, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_OUTPUT, nftb.InterceptionRuleKey, 0)
+       if r == nil {
+               t.Error("rule to queue connections not in the list")
+       }
+       if string(r.UserData) != nftb.InterceptionRuleKey {
+               t.Errorf("invalid UserData: %s", string(r.UserData))
+       }
+}
+
+func TestQueueDNSResponses(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       _, err := nftest.Fw.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+       if err != nil {
+               t.Error("pre step add_table() filter-inet failed")
+       }
+       chn := nftest.Fw.AddChain(
+               exprs.NFT_HOOK_INPUT, exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET,
+               nftables.ChainPriorityFilter,
+               nftables.ChainTypeFilter,
+               nftables.ChainHookInput,
+               nftables.ChainPolicyAccept)
+       if chn == nil {
+               t.Error("pre step add_chain() input-filter-inet failed")
+       }
+
+       if err1, err2 := nftest.Fw.QueueDNSResponses(true, true); err1 != nil && err2 != nil {
+               t.Errorf("rule to queue DNS responses not added: %s, %s", err1, err2)
+       }
+
+       r, _ := getRule(t, conn, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INPUT, nftb.InterceptionRuleKey, 0)
+       if r == nil {
+               t.Error("rule to queue DNS responses not in the list")
+       }
+       if string(r.UserData) != nftb.InterceptionRuleKey {
+               t.Errorf("invalid UserData: %s", string(r.UserData))
+       }
+
+       // nftables.DelRule() does not accept rule handles == 0
+       // https://github.com/google/nftables/blob/8f2d395e1089dea4966c483fbeae7e336917c095/rule.go#L200
+       // sometimes when adding this rule in new namespaces it's added with rule.Handle == 0, so it fails deleting the rule, thus failing the test.
+       // can it happen on "prod" environments?
+       /*if err1, err2 := nft.QueueDNSResponses(false, true); err1 != nil && err2 != nil {
+               t.Errorf("rule to queue DNS responses not deleted: %s, %s", err1, err2)
+       }
+       r, _ = getRule(t, conn, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INPUT, nftb.InterceptionRuleKey, 0)
+       if r != nil {
+               t.Error("rule to queue DNS responses should have been deleted")
+       }*/
+}
diff --git a/daemon/firewall/nftables/system.go b/daemon/firewall/nftables/system.go
new file mode 100644 (file)
index 0000000..08c08a2
--- /dev/null
@@ -0,0 +1,180 @@
+package nftables
+
+import (
+       "fmt"
+       "strings"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/config"
+       "github.com/evilsocket/opensnitch/daemon/firewall/iptables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+       "github.com/google/nftables/expr"
+       "github.com/google/uuid"
+)
+
+// store of tables added to the system
+type sysTablesT struct {
+       tables map[string]*nftables.Table
+       sync.RWMutex
+}
+
+func (t *sysTablesT) Add(name string, tbl *nftables.Table) {
+       t.Lock()
+       defer t.Unlock()
+       t.tables[name] = tbl
+}
+
+func (t *sysTablesT) Get(name string) *nftables.Table {
+       t.RLock()
+       defer t.RUnlock()
+       return t.tables[name]
+}
+
+func (t *sysTablesT) List() map[string]*nftables.Table {
+       t.RLock()
+       defer t.RUnlock()
+       return t.tables
+}
+
+func (t *sysTablesT) Del(name string) {
+       t.Lock()
+       defer t.Unlock()
+       delete(t.tables, name)
+}
+
+var (
+       logTag        = "nftables:"
+       sysTables     *sysTablesT
+       sysChains     *sync.Map
+       origSysChains map[string]*nftables.Chain
+       sysSets       []*nftables.Set
+)
+
+// InitMapsStore initializes internal stores of chains and maps.
+func InitMapsStore() {
+       sysTables = &sysTablesT{
+               tables: make(map[string]*nftables.Table),
+       }
+       sysChains = &sync.Map{}
+       origSysChains = make(map[string]*nftables.Chain)
+}
+
+// CreateSystemRule create the custom firewall chains and adds them to system.
+// nft insert rule ip opensnitch-filter opensnitch-input udp dport 1153
+func (n *Nft) CreateSystemRule(chain *config.FwChain, logErrors bool) bool {
+       if chain.IsInvalid() {
+               log.Warning("%s CreateSystemRule(), Chain's field Name and Family cannot be empty", logTag)
+               return false
+       }
+
+       tableName := chain.Table
+       n.AddTable(chain.Table, chain.Family)
+
+       // regular chains doesn't have a hook, nor a type
+       if chain.Hook == "" && chain.Type == "" {
+               n.addRegularChain(chain.Name, tableName, chain.Family)
+               return n.Commit()
+       }
+
+       chainPolicy := nftables.ChainPolicyAccept
+       if iptables.Action(strings.ToLower(chain.Policy)) == exprs.VERDICT_DROP {
+               chainPolicy = nftables.ChainPolicyDrop
+       }
+
+       chainHook := GetHook(chain.Hook)
+       chainPrio, chainType := GetChainPriority(chain.Family, chain.Type, chain.Hook)
+       if chainPrio == nil {
+               log.Warning("%s Invalid system firewall combination: %s, %s", logTag, chain.Type, chain.Hook)
+               return false
+       }
+
+       if ret := n.AddChain(chain.Name, chain.Table, chain.Family, chainPrio,
+               chainType, chainHook, chainPolicy); ret == nil {
+               log.Warning("%s error adding chain: %s, table: %s", logTag, chain.Name, chain.Table)
+               return false
+       }
+
+       return n.Commit()
+}
+
+// AddSystemRules creates the system firewall from configuration.
+func (n *Nft) AddSystemRules(reload, backupExistingChains bool) {
+       n.SysConfig.RLock()
+       defer n.SysConfig.RUnlock()
+
+       if n.SysConfig.Enabled == false {
+               log.Important("[nftables] AddSystemRules() fw disabled")
+               return
+       }
+       if backupExistingChains {
+               n.backupExistingChains()
+       }
+
+       for _, fwCfg := range n.SysConfig.SystemRules {
+               for _, chain := range fwCfg.Chains {
+                       if !n.CreateSystemRule(chain, true) {
+                               log.Info("createSystem failed: %s %s", chain.Name, chain.Table)
+                               continue
+                       }
+                       for i := len(chain.Rules) - 1; i >= 0; i-- {
+                               if chain.Rules[i].UUID == "" {
+                                       uuid := uuid.New()
+                                       chain.Rules[i].UUID = uuid.String()
+                               }
+                               if chain.Rules[i].Enabled {
+                                       if err4, _ := n.AddSystemRule(chain.Rules[i], chain); err4 != nil {
+                                               n.SendError(fmt.Sprintf("%s (%s)", err4, chain.Rules[i].UUID))
+                                       }
+                               }
+                       }
+               }
+       }
+}
+
+// DeleteSystemRules deletes the system rules.
+// If force is false and the rule has not been previously added,
+// it won't try to delete the tables and chains. Otherwise it'll try to delete them.
+func (n *Nft) DeleteSystemRules(force, restoreExistingChains, logErrors bool) {
+       n.Lock()
+       defer n.Unlock()
+
+       if err := n.delRulesByKey(SystemRuleKey); err != nil {
+               log.Warning("error deleting interception rules: %s", err)
+       }
+
+       if restoreExistingChains {
+               n.restoreBackupChains()
+       }
+       if force {
+               n.DelSystemTables()
+       }
+}
+
+// AddSystemRule inserts a new rule.
+func (n *Nft) AddSystemRule(rule *config.FwRule, chain *config.FwChain) (err4, err6 error) {
+       n.Lock()
+       defer n.Unlock()
+       exprList := []expr.Any{}
+
+       for _, expression := range rule.Expressions {
+               exprsOfRule := n.parseExpression(chain.Table, chain.Name, chain.Family, expression)
+               if exprsOfRule == nil {
+                       return fmt.Errorf("%s invalid rule parameters: %v", rule.UUID, expression), nil
+               }
+               exprList = append(exprList, *exprsOfRule...)
+       }
+       if len(exprList) > 0 {
+               exprVerdict := exprs.NewExprVerdict(rule.Target, rule.TargetParameters)
+               if exprVerdict == nil {
+                       return fmt.Errorf("%s invalid verdict %s %s", rule.UUID, rule.Target, rule.TargetParameters), nil
+               }
+               exprList = append(exprList, *exprVerdict...)
+               if err := n.InsertRule(chain.Name, chain.Table, chain.Family, rule.Position, &exprList); err != nil {
+                       return err, nil
+               }
+       }
+
+       return nil, nil
+}
diff --git a/daemon/firewall/nftables/system_test.go b/daemon/firewall/nftables/system_test.go
new file mode 100644 (file)
index 0000000..59d2f01
--- /dev/null
@@ -0,0 +1,167 @@
+package nftables_test
+
+import (
+       "testing"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+)
+
+type sysChainsListT struct {
+       family        string
+       table         string
+       chain         string
+       expectedRules int
+}
+
+func TestAddSystemRules(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       cfg, err := nftest.Fw.NewSystemFwConfig(nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback)
+       if err != nil {
+               t.Logf("Error creating fw config: %s", err)
+       }
+
+       cfg.SetFile("./testdata/test-sysfw-conf.json")
+       if err := cfg.LoadDiskConfiguration(false); err != nil {
+               t.Errorf("Error loading config from disk: %s", err)
+       }
+
+       nftest.Fw.AddSystemRules(false, false)
+
+       rules, _ := getRulesList(t, conn, exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INPUT)
+       // 3 rules in total, 1 disabled.
+       if len(rules) != 1 {
+               t.Errorf("test-load-conf.json mangle-output should contain only 3 rules, no -> %d", len(rules))
+               for _, r := range rules {
+                       t.Logf("%+v", r)
+               }
+       }
+
+       rules, _ = getRulesList(t, conn, exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_OUTPUT)
+       // 3 rules in total, 1 disabled.
+       if len(rules) != 3 {
+               t.Errorf("test-load-conf.json mangle-output should contain only 3 rules, no -> %d", len(rules))
+               for _, r := range rules {
+                       t.Log(r)
+               }
+       }
+
+       rules, _ = getRulesList(t, conn, exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_FORWARD)
+       // 3 rules in total, 1 disabled.
+       if len(rules) != 1 {
+               t.Errorf("test-load-conf.json mangle-output should contain only 3 rules, no -> %d", len(rules))
+               for _, r := range rules {
+                       t.Log(r)
+               }
+       }
+
+}
+
+func TestFwConfDisabled(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       cfg, err := nftest.Fw.NewSystemFwConfig(nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback)
+       if err != nil {
+               t.Logf("Error creating fw config: %s", err)
+       }
+
+       cfg.SetFile("./testdata/test-sysfw-conf.json")
+       if err := cfg.LoadDiskConfiguration(false); err != nil {
+               t.Errorf("Error loading config from disk: %s", err)
+       }
+
+       nftest.Fw.AddSystemRules(false, false)
+
+       tests := []sysChainsListT{
+               {
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_OUTPUT, 3,
+               },
+               {
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_FORWARD, 1,
+               },
+               {
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INPUT, 1,
+               },
+       }
+
+       for _, tt := range tests {
+               rules, _ := getRulesList(t, conn, tt.family, tt.table, tt.chain)
+               if len(rules) != 0 {
+                       t.Logf("%d rules found, there should be 0", len(rules))
+               }
+       }
+}
+
+func TestDeleteSystemRules(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       cfg, err := nftest.Fw.NewSystemFwConfig(nftest.Fw.PreloadConfCallback, nftest.Fw.ReloadConfCallback)
+       if err != nil {
+               t.Logf("Error creating fw config: %s", err)
+       }
+
+       cfg.SetFile("./testdata/test-sysfw-conf.json")
+       if err := cfg.LoadDiskConfiguration(false); err != nil {
+               t.Errorf("Error loading config from disk: %s", err)
+       }
+
+       nftest.Fw.AddSystemRules(false, false)
+
+       tests := []sysChainsListT{
+               {
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_OUTPUT, 3,
+               },
+               {
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_FORWARD, 1,
+               },
+               {
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INPUT, 1,
+               },
+       }
+       for _, tt := range tests {
+               rules, _ := getRulesList(t, conn, tt.family, tt.table, tt.chain)
+               if len(rules) != tt.expectedRules {
+                       t.Errorf("%d rules found, there should be %d", len(rules), tt.expectedRules)
+               }
+       }
+
+       t.Run("test-delete-system-rules", func(t *testing.T) {
+               nftest.Fw.DeleteSystemRules(false, false, true)
+               for _, tt := range tests {
+                       rules, _ := getRulesList(t, conn, tt.family, tt.table, tt.chain)
+                       if len(rules) != 0 {
+                               t.Errorf("%d rules found, there should be 0", len(rules))
+                       }
+
+                       tbl := nftest.Fw.GetTable(tt.table, tt.family)
+                       if tbl == nil {
+                               t.Errorf("table %s-%s should exist", tt.table, tt.family)
+                       }
+
+                       /*chn := nft.getChain(tt.chain, tbl, tt.family)
+                       if chn == nil {
+                               if chains, err := conn.ListChains(); err == nil {
+                                       for _, c := range chains {
+                                       }
+                               }
+                               t.Errorf("chain %s-%s-%s should exist", tt.family, tt.table, tt.chain)
+                       }*/
+               }
+
+       })
+       t.Run("test-delete-system-rules+chains", func(t *testing.T) {
+       })
+}
diff --git a/daemon/firewall/nftables/tables.go b/daemon/firewall/nftables/tables.go
new file mode 100644 (file)
index 0000000..e613a37
--- /dev/null
@@ -0,0 +1,90 @@
+package nftables
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+)
+
+// AddTable adds a new table to nftables.
+func (n *Nft) AddTable(name, family string) (*nftables.Table, error) {
+       famCode := GetFamilyCode(family)
+       tbl := &nftables.Table{
+               Family: famCode,
+               Name:   name,
+       }
+       n.Conn.AddTable(tbl)
+
+       if !n.Commit() {
+               return nil, fmt.Errorf("%s error adding system firewall table: %s, family: %s (%d)", logTag, name, family, famCode)
+       }
+       key := getTableKey(name, family)
+       sysTables.Add(key, tbl)
+       return tbl, nil
+}
+
+// GetTable retrieves an already added table to the system.
+func (n *Nft) GetTable(name, family string) *nftables.Table {
+       return sysTables.Get(getTableKey(name, family))
+}
+
+func getTableKey(name string, family interface{}) string {
+       return fmt.Sprint(name, "-", family)
+}
+
+// AddInterceptionTables adds the needed tables to intercept traffic.
+func (n *Nft) AddInterceptionTables() error {
+       if _, err := n.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET); err != nil {
+               return err
+       }
+       if _, err := n.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET); err != nil {
+               return err
+       }
+       return nil
+}
+
+// Contrary to iptables, in nftables there're no predefined rules.
+// Convention is though to use the iptables names by default.
+// We need at least: mangle and filter tables, inet family (IPv4 and IPv6).
+func (n *Nft) addSystemTables() {
+       n.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
+       n.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+}
+
+// return the number of rules that we didn't add.
+func (n *Nft) nonSystemRules(tbl *nftables.Table) int {
+       chains, err := n.Conn.ListChains()
+       if err != nil {
+               return -1
+       }
+       t := 0
+       for _, c := range chains {
+               if tbl.Name != c.Table.Name && tbl.Family != c.Table.Family {
+                       continue
+               }
+               rules, err := n.Conn.GetRule(c.Table, c)
+               if err != nil {
+                       return -1
+               }
+               t += len(rules)
+       }
+
+       return t
+}
+
+// DelSystemTables deletes tables created from fw configuration.
+func (n *Nft) DelSystemTables() {
+       for k, tbl := range sysTables.List() {
+               if n.nonSystemRules(tbl) != 0 {
+                       continue
+               }
+               n.Conn.DelTable(tbl)
+               if !n.Commit() {
+                       log.Warning("error deleting system table: %s", k)
+                       continue
+               }
+               sysTables.Del(k)
+       }
+}
diff --git a/daemon/firewall/nftables/tables_test.go b/daemon/firewall/nftables/tables_test.go
new file mode 100644 (file)
index 0000000..36588c8
--- /dev/null
@@ -0,0 +1,113 @@
+package nftables_test
+
+import (
+       "testing"
+
+       nftb "github.com/evilsocket/opensnitch/daemon/firewall/nftables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest"
+       "github.com/google/nftables"
+)
+
+func tableExists(t *testing.T, conn *nftables.Conn, origtbl *nftables.Table, family string) bool {
+       tables, err := conn.ListTablesOfFamily(
+               nftb.GetFamilyCode(family),
+       )
+       if err != nil {
+               return false
+       }
+       found := false
+       for _, tbl := range tables {
+               if origtbl != nil && tbl.Name == origtbl.Name {
+                       found = true
+                       break
+               }
+       }
+       return found
+}
+
+func TestAddTable(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       t.Run("inet family", func(t *testing.T) {
+               tblxxx, err := nftest.Fw.AddTable("xxx", exprs.NFT_FAMILY_INET)
+               if err != nil {
+                       t.Error("table xxx-inet not added:", err)
+               }
+               if tableExists(t, nftest.Fw.Conn, tblxxx, exprs.NFT_FAMILY_INET) == false {
+                       t.Error("table xxx-inet not in the list")
+               }
+
+               nftest.Fw.DelSystemTables()
+               if tableExists(t, nftest.Fw.Conn, tblxxx, exprs.NFT_FAMILY_INET) {
+                       t.Error("table xxx-inet still exists")
+               }
+       })
+
+       t.Run("ip family", func(t *testing.T) {
+               tblxxx, err := nftest.Fw.AddTable("xxx", exprs.NFT_FAMILY_IP)
+               if err != nil {
+                       t.Error("table xxx-ip not added:", err)
+               }
+               if tableExists(t, nftest.Fw.Conn, tblxxx, exprs.NFT_FAMILY_IP) == false {
+                       t.Error("table xxx-ip not in the list")
+               }
+
+               nftest.Fw.DelSystemTables()
+               if tableExists(t, nftest.Fw.Conn, tblxxx, exprs.NFT_FAMILY_IP) {
+                       t.Errorf("table xxx-ip still exists:") // %+v", sysTables)
+               }
+       })
+
+       t.Run("ip6 family", func(t *testing.T) {
+               tblxxx, err := nftest.Fw.AddTable("xxx", exprs.NFT_FAMILY_IP6)
+               if err != nil {
+                       t.Error("table xxx-ip6 not added:", err)
+               }
+               if tableExists(t, nftest.Fw.Conn, tblxxx, exprs.NFT_FAMILY_IP6) == false {
+                       t.Error("table xxx-ip6 not in the list")
+               }
+
+               nftest.Fw.DelSystemTables()
+               if tableExists(t, nftest.Fw.Conn, tblxxx, exprs.NFT_FAMILY_IP6) {
+                       t.Errorf("table xxx-ip6 still exists:") // %+v", sysTables)
+               }
+       })
+}
+
+// TestAddInterceptionTables checks if the needed tables have been created.
+// We use 2: mangle-inet for intercepting outbound connections, and filter-inet for DNS responses interception
+func TestAddInterceptionTables(t *testing.T) {
+       nftest.SkipIfNotPrivileged(t)
+
+       conn, newNS := nftest.OpenSystemConn(t)
+       defer nftest.CleanupSystemConn(t, newNS)
+       nftest.Fw.Conn = conn
+
+       if err := nftest.Fw.AddInterceptionTables(); err != nil {
+               t.Errorf("addInterceptionTables() error: %s", err)
+       }
+
+       t.Run("mangle-inet", func(t *testing.T) {
+               tblmangle := nftest.Fw.GetTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET)
+               if tblmangle == nil {
+                       t.Error("interception table mangle-inet not in the list")
+               }
+               if tableExists(t, nftest.Fw.Conn, tblmangle, exprs.NFT_FAMILY_INET) == false {
+                       t.Error("table mangle-inet not in the list")
+               }
+       })
+       t.Run("filter-inet", func(t *testing.T) {
+               tblfilter := nftest.Fw.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET)
+               if tblfilter == nil {
+                       t.Error("interception table filter-inet not in the list")
+               }
+               if tableExists(t, nftest.Fw.Conn, tblfilter, exprs.NFT_FAMILY_INET) == false {
+                       t.Error("table filter-inet not in the list")
+               }
+       })
+}
diff --git a/daemon/firewall/nftables/testdata/test-sysfw-conf.json b/daemon/firewall/nftables/testdata/test-sysfw-conf.json
new file mode 100644 (file)
index 0000000..089b204
--- /dev/null
@@ -0,0 +1,159 @@
+{
+  "Enabled": true,
+  "Version": 1,
+  "SystemRules": [
+    {
+      "Chains": [
+        {
+          "Name": "input",
+          "Table": "filter",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "filter",
+          "Hook": "input",
+          "Policy": "accept",
+          "Rules": [
+            {
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Allow SSH server connections when input policy is DROP",
+              "Parameters": "",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "tcp",
+                    "Values": [
+                      {
+                        "Key": "dport",
+                        "Value": "22"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            } 
+          ]
+        },
+        {
+          "Name": "output",
+          "Table": "mangle",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "mangle",
+          "Hook": "output",
+          "Policy": "accept",
+          "Rules": [
+            {
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Allow ICMP",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "icmp",
+                    "Values": [
+                      {
+                        "Key": "type",
+                        "Value": "echo-request"
+                      },
+                      {
+                        "Key": "type",
+                        "Value": "echo-reply"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            },
+            {
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Allow ICMPv6",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "icmpv6",
+                    "Values": [
+                      {
+                        "Key": "type",
+                        "Value": "echo-request"
+                      },
+                      {
+                        "Key": "type",
+                        "Value": "echo-reply"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            },
+            {
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Exclude WireGuard VPN from being intercepted",
+              "Parameters": "",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "udp",
+                    "Values": [
+                      {
+                        "Key": "dport",
+                        "Value": "51820"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            }
+          ]
+        },
+        {
+          "Name": "forward",
+          "Table": "mangle",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "mangle",
+          "Hook": "forward",
+          "Policy": "accept",
+          "Rules": [
+            {
+              "UUID": "7d7394e1-100d-4b87-a90a-cd68c46edb0b",
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Intercept forwarded connections (docker, etc)",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "ct",
+                    "Values": [
+                      {
+                        "Key": "state",
+                        "Value": "new"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "queue",
+              "TargetParameters": "num 0"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/daemon/firewall/nftables/utils.go b/daemon/firewall/nftables/utils.go
new file mode 100644 (file)
index 0000000..dbb3d19
--- /dev/null
@@ -0,0 +1,173 @@
+package nftables
+
+import (
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/nftables"
+)
+
+func GetFamilyCode(family string) nftables.TableFamily {
+       famCode := nftables.TableFamilyINet
+       switch family {
+       // [filter]: prerouting forward input   output  postrouting
+       // [nat]: prerouting, input     output  postrouting
+       // [route]: output
+       case exprs.NFT_FAMILY_IP6:
+               famCode = nftables.TableFamilyIPv6
+       case exprs.NFT_FAMILY_IP:
+               famCode = nftables.TableFamilyIPv4
+       case exprs.NFT_FAMILY_BRIDGE:
+               // [filter]: prerouting forward input   output  postrouting
+               famCode = nftables.TableFamilyBridge
+       case exprs.NFT_FAMILY_ARP:
+               // [filter]: input      output
+               famCode = nftables.TableFamilyARP
+       case exprs.NFT_FAMILY_NETDEV:
+               // [filter]: egress, ingress
+               famCode = nftables.TableFamilyNetdev
+       }
+
+       return famCode
+}
+
+func GetHook(chain string) *nftables.ChainHook {
+       hook := nftables.ChainHookOutput
+
+       // https://github.com/google/nftables/blob/master/chain.go#L33
+       switch strings.ToLower(chain) {
+       case exprs.NFT_HOOK_INPUT:
+               hook = nftables.ChainHookInput
+       case exprs.NFT_HOOK_PREROUTING:
+               hook = nftables.ChainHookPrerouting
+       case exprs.NFT_HOOK_POSTROUTING:
+               hook = nftables.ChainHookPostrouting
+       case exprs.NFT_HOOK_FORWARD:
+               hook = nftables.ChainHookForward
+       case exprs.NFT_HOOK_INGRESS:
+               hook = nftables.ChainHookIngress
+       }
+
+       return hook
+}
+
+// GetChainPriority gets the corresponding priority for the given chain, based
+// on the following configuration matrix:
+// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
+// https://github.com/google/nftables/blob/master/chain.go#L48
+// man nft (table 6.)
+func GetChainPriority(family, cType, hook string) (*nftables.ChainPriority, nftables.ChainType) {
+       // types: route, nat, filter
+       chainType := nftables.ChainTypeFilter
+       // priorities: raw, conntrack, mangle, natdest, filter, security
+       chainPrio := nftables.ChainPriorityFilter
+
+       family = strings.ToLower(family)
+       cType = strings.ToLower(cType)
+       hook = strings.ToLower(hook)
+
+       // constraints
+       // https://www.netfilter.org/projects/nftables/manpage.html#lbAQ
+       if (cType == exprs.NFT_CHAIN_NATDEST || cType == exprs.NFT_CHAIN_NATSOURCE) && hook == exprs.NFT_HOOK_FORWARD {
+               log.Warning("[nftables] invalid nat combination of tables and hooks. chain: %s, hook: %s", cType, hook)
+               return nil, chainType
+       }
+       if family == exprs.NFT_FAMILY_NETDEV && (cType != exprs.NFT_CHAIN_FILTER || hook != exprs.NFT_HOOK_INGRESS) {
+               log.Warning("[nftables] invalid netdev combination of tables and hooks. chain: %s, hook: %s", cType, hook)
+               return nil, chainType
+       }
+       if family == exprs.NFT_FAMILY_ARP && (cType != exprs.NFT_CHAIN_FILTER || (hook != exprs.NFT_HOOK_OUTPUT && hook != exprs.NFT_HOOK_INPUT)) {
+               log.Warning("[nftables] invalid arp combination of tables and hooks. chain: %s, hook: %s", cType, hook)
+               return nil, chainType
+       }
+       if family == exprs.NFT_FAMILY_BRIDGE && (cType != exprs.NFT_CHAIN_FILTER || (hook == exprs.NFT_HOOK_EGRESS || hook == exprs.NFT_HOOK_INGRESS)) {
+               log.Warning("[nftables] invalid bridge combination of tables and hooks. chain: %s, hook: %s", cType, hook)
+               return nil, chainType
+       }
+
+       // Standard priority names, family and hook compatibility matrix
+       // https://www.netfilter.org/projects/nftables/manpage.html#lbAQ
+
+       switch cType {
+       case exprs.NFT_CHAIN_FILTER:
+               if family == exprs.NFT_FAMILY_BRIDGE {
+                       // bridge       all     filter  -200    NF_BR_PRI_FILTER_BRIDGED
+                       chainPrio = nftables.ChainPriorityConntrack
+                       switch hook {
+                       case exprs.NFT_HOOK_PREROUTING: // -300
+                               chainPrio = nftables.ChainPriorityRaw
+                       case exprs.NFT_HOOK_OUTPUT: // -100
+                               chainPrio = nftables.ChainPriorityNATSource
+                       case exprs.NFT_HOOK_POSTROUTING: // 300
+                               chainPrio = nftables.ChainPriorityConntrackHelper
+                       }
+               }
+       case exprs.NFT_CHAIN_MANGLE:
+               // hooks: all
+               // XXX: check hook input?
+               chainPrio = nftables.ChainPriorityMangle
+               // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_types
+               // (...) equivalent semantics to the mangle table but only for the output hook (for other hooks use type filter instead).
+
+               // Despite of what is said on the wiki, mangle chains must be of filter type,
+               // otherwise on some kernels (4.19.x) table MANGLE hook OUTPUT chain is not created
+               chainType = nftables.ChainTypeFilter
+
+       case exprs.NFT_CHAIN_RAW:
+               // hook: all
+               chainPrio = nftables.ChainPriorityRaw
+
+       case exprs.NFT_CHAIN_CONNTRACK:
+               chainPrio, chainType = GetConntrackPriority(hook)
+
+       case exprs.NFT_CHAIN_NATDEST:
+               // hook: prerouting
+               chainPrio = nftables.ChainPriorityNATDest
+               switch hook {
+               case exprs.NFT_HOOK_OUTPUT:
+                       chainPrio = nftables.ChainPriorityNATSource
+               }
+               chainType = nftables.ChainTypeNAT
+
+       case exprs.NFT_CHAIN_NATSOURCE:
+               // hook: postrouting
+               chainPrio = nftables.ChainPriorityNATSource
+               chainType = nftables.ChainTypeNAT
+
+       case exprs.NFT_CHAIN_SECURITY:
+               // hook: all
+               chainPrio = nftables.ChainPrioritySecurity
+
+       case exprs.NFT_CHAIN_SELINUX:
+               // hook: all
+               if hook != exprs.NFT_HOOK_POSTROUTING {
+                       chainPrio = nftables.ChainPrioritySELinuxLast
+               } else {
+                       chainPrio = nftables.ChainPrioritySELinuxFirst
+               }
+       }
+
+       return chainPrio, chainType
+}
+
+// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
+func GetConntrackPriority(hook string) (*nftables.ChainPriority, nftables.ChainType) {
+       chainType := nftables.ChainTypeFilter
+       chainPrio := nftables.ChainPriorityConntrack
+       switch hook {
+       case exprs.NFT_HOOK_PREROUTING:
+               chainPrio = nftables.ChainPriorityConntrack
+               // ChainTypeNAT not allowed here
+       case exprs.NFT_HOOK_OUTPUT:
+               chainPrio = nftables.ChainPriorityNATSource // 100 - ChainPriorityConntrack
+       case exprs.NFT_HOOK_POSTROUTING:
+               chainPrio = nftables.ChainPriorityConntrackHelper
+               chainType = nftables.ChainTypeNAT
+       case exprs.NFT_HOOK_INPUT:
+               // can also be hook == NFT_HOOK_POSTROUTING
+               chainPrio = nftables.ChainPriorityConntrackConfirm
+       }
+
+       return chainPrio, chainType
+}
diff --git a/daemon/firewall/nftables/utils_test.go b/daemon/firewall/nftables/utils_test.go
new file mode 100644 (file)
index 0000000..999a44e
--- /dev/null
@@ -0,0 +1,221 @@
+package nftables_test
+
+import (
+       "testing"
+
+       nftb "github.com/evilsocket/opensnitch/daemon/firewall/nftables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs"
+       "github.com/google/nftables"
+)
+
+type chainPrioT struct {
+       test        string
+       errorReason string
+       family      string
+       chain       string
+       hook        string
+       checkEqual  bool
+       chainPrio   *nftables.ChainPriority
+       chainType   nftables.ChainType
+}
+
+// TestGetConntrackPriority test basic Conntrack chains priority configurations.
+// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
+func TestGetConntrackPriority(t *testing.T) {
+
+       t.Run("hook-prerouting", func(t *testing.T) {
+               cprio, ctype := nftb.GetConntrackPriority(exprs.NFT_HOOK_PREROUTING)
+               if cprio != nftables.ChainPriorityConntrack && ctype != nftables.ChainTypeFilter {
+                       t.Errorf("invalid conntrack priority or type for hook PREROUTING: %+v, %+v", cprio, ctype)
+               }
+       })
+
+       t.Run("hook-output", func(t *testing.T) {
+               cprio, ctype := nftb.GetConntrackPriority(exprs.NFT_HOOK_OUTPUT)
+               if cprio != nftables.ChainPriorityNATSource && ctype != nftables.ChainTypeFilter {
+                       t.Errorf("invalid conntrack priority or type for hook OUTPUT: %+v, %+v", cprio, ctype)
+               }
+       })
+
+       t.Run("hook-postrouting", func(t *testing.T) {
+               cprio, ctype := nftb.GetConntrackPriority(exprs.NFT_HOOK_POSTROUTING)
+               if cprio != nftables.ChainPriorityConntrackHelper && ctype != nftables.ChainTypeNAT {
+                       t.Errorf("invalid conntrack priority or type for hook POSTROUTING: %+v, %+v", cprio, ctype)
+               }
+       })
+
+       t.Run("hook-input", func(t *testing.T) {
+               cprio, ctype := nftb.GetConntrackPriority(exprs.NFT_HOOK_INPUT)
+               if cprio != nftables.ChainPriorityConntrackConfirm && ctype != nftables.ChainTypeFilter {
+                       t.Errorf("invalid conntrack priority or type for hook INPUT: %+v, %+v", cprio, ctype)
+               }
+       })
+
+}
+
+// https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks#Priority_within_hook
+// https://github.com/google/nftables/blob/master/chain.go#L48
+// man nft (table 6.)
+func TestGetChainPriority(t *testing.T) {
+       matrixTests := []chainPrioT{
+               // https://wiki.nftables.org/wiki-nftables/index.php/Configuring_chains#Base_chain_types
+               // (...) equivalent semantics to the mangle table but only for the output hook (for other hooks use type filter instead).
+
+               // Despite of what is said on the wiki, mangle chains must be of filter type,
+               // otherwise on some kernels (4.19.x) table MANGLE hook OUTPUT chain is not created
+               {
+                       "inet-mangle-output",
+                       "invalid MANGLE chain priority or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_MANGLE, exprs.NFT_HOOK_OUTPUT,
+                       true,
+                       nftables.ChainPriorityMangle, nftables.ChainTypeFilter,
+               },
+               {
+                       "inet-natdest-output",
+                       "invalid NATDest-output chain priority or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATDEST, exprs.NFT_HOOK_OUTPUT,
+                       true,
+                       nftables.ChainPriorityNATSource, nftables.ChainTypeNAT,
+               },
+               {
+                       "inet-natdest-prerouting",
+                       "invalid NATDest-prerouting chain priority or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATDEST, exprs.NFT_HOOK_PREROUTING,
+                       true,
+                       nftables.ChainPriorityNATDest, nftables.ChainTypeNAT,
+               },
+               {
+                       "inet-natsource-postrouting",
+                       "invalid NATSource-postrouting chain priority or type: %+v-%+v, %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATSOURCE, exprs.NFT_HOOK_POSTROUTING,
+                       true,
+                       nftables.ChainPriorityNATSource, nftables.ChainTypeNAT,
+               },
+
+               // constraints
+               // https://www.netfilter.org/projects/nftables/manpage.html#lbAQ
+               {
+                       "inet-natdest-forward",
+                       "invalid natdest-forward chain: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATDEST, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil, nftables.ChainTypeFilter,
+               },
+               {
+                       "inet-natsource-forward",
+                       "invalid natsource-forward chain: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATSOURCE, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil, nftables.ChainTypeFilter,
+               },
+               {
+                       "netdev-filter-ingress",
+                       "invalid netdev chain prio or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_NETDEV, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INGRESS,
+                       true,
+                       nftables.ChainPriorityFilter, nftables.ChainTypeFilter,
+               },
+               {
+                       "arp-filter-input",
+                       "invalid arp chain prio or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_ARP, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_INPUT,
+                       true,
+                       nftables.ChainPriorityFilter, nftables.ChainTypeFilter,
+               },
+               {
+                       "bridge-filter-prerouting",
+                       "invalid bridge-prerouting chain prio or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_BRIDGE, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_PREROUTING,
+                       true,
+                       nftables.ChainPriorityRaw, nftables.ChainTypeFilter,
+               },
+               {
+                       "bridge-filter-output",
+                       "invalid bridge-output chain prio or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_BRIDGE, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_OUTPUT,
+                       true,
+                       nftables.ChainPriorityNATSource, nftables.ChainTypeFilter,
+               },
+               {
+                       "bridge-filter-postrouting",
+                       "invalid bridge-postrouting chain prio or type: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_BRIDGE, exprs.NFT_CHAIN_FILTER, exprs.NFT_HOOK_POSTROUTING,
+                       true,
+                       nftables.ChainPriorityConntrackHelper, nftables.ChainTypeFilter,
+               },
+       }
+
+       for _, testChainPrio := range matrixTests {
+               t.Run(testChainPrio.test, func(t *testing.T) {
+                       chainPrio, chainType := nftb.GetChainPriority(testChainPrio.family, testChainPrio.chain, testChainPrio.hook)
+
+                       if testChainPrio.checkEqual {
+                               if chainPrio != testChainPrio.chainPrio && chainType != testChainPrio.chainType {
+                                       t.Errorf(testChainPrio.errorReason, chainPrio, chainType, testChainPrio.chainPrio, testChainPrio.chainType)
+                               }
+                       } else {
+                               if chainPrio == testChainPrio.chainPrio && chainType == testChainPrio.chainType {
+                                       t.Errorf(testChainPrio.errorReason, chainPrio, chainType, testChainPrio.chainPrio, testChainPrio.chainType)
+                               }
+                       }
+               })
+       }
+
+}
+
+func TestInvalidChainPriority(t *testing.T) {
+       matrixTests := []chainPrioT{
+               {
+                       "inet-natdest-forward",
+                       "natdest-forward chain should be invalid: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATDEST, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil, nftables.ChainTypeFilter,
+               },
+               {
+                       "inet-natsource-forward",
+                       "natsource-forward chain should be invalid: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_INET, exprs.NFT_CHAIN_NATSOURCE, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil, nftables.ChainTypeFilter,
+               },
+               {
+                       "netdev-natsource-forward",
+                       "netdev chain should be invalid: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_NETDEV, exprs.NFT_CHAIN_NATSOURCE, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil,
+                       nftables.ChainTypeFilter,
+               },
+               {
+                       "arp-natsource-forward",
+                       "arp chain should be invalid: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_ARP, exprs.NFT_CHAIN_NATSOURCE, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil, nftables.ChainTypeFilter,
+               },
+               {
+                       "bridge-natsource-forward",
+                       "bridge chain should be invalid: %+v-%+v <-> %v-%v",
+                       exprs.NFT_FAMILY_ARP, exprs.NFT_CHAIN_NATSOURCE, exprs.NFT_HOOK_FORWARD,
+                       true,
+                       nil, nftables.ChainTypeFilter,
+               },
+       }
+
+       for _, testChainPrio := range matrixTests {
+               t.Run(testChainPrio.test, func(t *testing.T) {
+                       chainPrio, chainType := nftb.GetChainPriority(testChainPrio.family, testChainPrio.chain, testChainPrio.hook)
+
+                       if testChainPrio.checkEqual {
+                               if chainPrio != testChainPrio.chainPrio && chainType != testChainPrio.chainType {
+                               }
+                       } else {
+                               if chainPrio == testChainPrio.chainPrio && chainType == testChainPrio.chainType {
+                                       t.Errorf(testChainPrio.errorReason, chainPrio, chainType, testChainPrio.chainPrio, testChainPrio.chainType)
+                               }
+                       }
+               })
+       }
+
+}
diff --git a/daemon/firewall/rules.go b/daemon/firewall/rules.go
new file mode 100644 (file)
index 0000000..f764479
--- /dev/null
@@ -0,0 +1,160 @@
+package firewall
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/firewall/common"
+       "github.com/evilsocket/opensnitch/daemon/firewall/iptables"
+       "github.com/evilsocket/opensnitch/daemon/firewall/nftables"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// Firewall is the interface that all firewalls (iptables, nftables) must implement.
+type Firewall interface {
+       Init(*int)
+       Stop()
+       Name() string
+       IsRunning() bool
+       SetQueueNum(num *int)
+
+       SaveConfiguration(rawConfig string) error
+
+       EnableInterception()
+       DisableInterception(bool)
+       QueueDNSResponses(bool, bool) (error, error)
+       QueueConnections(bool, bool) (error, error)
+       CleanRules(bool)
+
+       AddSystemRules(bool, bool)
+       DeleteSystemRules(bool, bool, bool)
+
+       Serialize() (*protocol.SysFirewall, error)
+       Deserialize(sysfw *protocol.SysFirewall) ([]byte, error)
+
+       ErrorsChan() <-chan string
+       ErrChanEmpty() bool
+}
+
+var (
+       fw       Firewall
+       queueNum = 0
+)
+
+// Init initializes the firewall and loads firewall rules.
+// We'll try to use the firewall configured in the configuration (iptables/nftables).
+// If iptables is not installed, we can add nftables rules directly to the kernel,
+// without relying on any binaries.
+func Init(fwType string, qNum *int) (err error) {
+       if fwType == iptables.Name {
+               fw, err = iptables.Fw()
+               if err != nil {
+                       log.Warning("iptables not available: %s", err)
+               }
+       }
+
+       if fwType == nftables.Name || err != nil {
+               fw, err = nftables.Fw()
+               if err != nil {
+                       log.Warning("nftables not available: %s", err)
+               }
+       }
+
+       if err != nil {
+               return fmt.Errorf("firewall error: %s, not iptables nor nftables are available or are usable. Please, report it on github", err)
+       }
+
+       if fw == nil {
+               return fmt.Errorf("Firewall not initialized")
+       }
+       fw.Stop()
+       fw.Init(qNum)
+       queueNum = *qNum
+
+       log.Info("Using %s firewall", fw.Name())
+
+       return
+}
+
+// IsRunning returns if the firewall is running or not.
+func IsRunning() bool {
+       return fw != nil && fw.IsRunning()
+}
+
+// ErrorsChan returns the channel where the errors are sent to.
+func ErrorsChan() <-chan string {
+       return fw.ErrorsChan()
+}
+
+// ErrChanEmpty checks if the errors channel is empty.
+func ErrChanEmpty() bool {
+       return fw.ErrChanEmpty()
+}
+
+// CleanRules deletes the rules we added.
+func CleanRules(logErrors bool) {
+       if fw == nil {
+               return
+       }
+       fw.CleanRules(logErrors)
+}
+
+// ChangeFw stops current firewall and initializes a new one.
+func ChangeFw(fwtype string) (err error) {
+       Stop()
+       err = Init(fwtype, &queueNum)
+       return
+}
+
+// Reload deletes existing firewall rules and readds them.
+func Reload() {
+       fw.Stop()
+       fw.Init(&queueNum)
+}
+
+// ReloadSystemRules deletes existing rules, and add them again
+func ReloadSystemRules() {
+       fw.DeleteSystemRules(!common.ForcedDelRules, common.RestoreChains, true)
+       fw.AddSystemRules(common.ReloadRules, common.BackupChains)
+}
+
+// EnableInterception removes the rules to intercept outbound connections.
+func EnableInterception() error {
+       if fw == nil {
+               return fmt.Errorf("firewall not initialized when trying to enable interception, report please")
+       }
+       fw.EnableInterception()
+       return nil
+}
+
+// DisableInterception removes the rules to intercept outbound connections.
+func DisableInterception() error {
+       if fw == nil {
+               return fmt.Errorf("firewall not initialized when trying to disable interception, report please")
+       }
+       fw.DisableInterception(true)
+       return nil
+}
+
+// Stop deletes the firewall rules, allowing network traffic.
+func Stop() {
+       if fw == nil {
+               return
+       }
+       fw.Stop()
+}
+
+// SaveConfiguration saves configuration string to disk
+func SaveConfiguration(rawConfig []byte) error {
+       return fw.SaveConfiguration(string(rawConfig))
+}
+
+// Serialize transforms firewall json configuration to protobuf
+func Serialize() (*protocol.SysFirewall, error) {
+       return fw.Serialize()
+}
+
+// Deserialize transforms firewall json configuration to protobuf
+func Deserialize(sysfw *protocol.SysFirewall) ([]byte, error) {
+       return fw.Deserialize(sysfw)
+}
diff --git a/daemon/go.mod b/daemon/go.mod
new file mode 100644 (file)
index 0000000..b6e4a7f
--- /dev/null
@@ -0,0 +1,33 @@
+module github.com/evilsocket/opensnitch/daemon
+
+go 1.17
+
+require (
+       github.com/fsnotify/fsnotify v1.4.7
+       github.com/golang/protobuf v1.5.0
+       github.com/google/gopacket v1.1.14
+       github.com/google/nftables v0.1.0
+       github.com/google/uuid v1.3.0
+       github.com/iovisor/gobpf v0.2.0
+       github.com/varlink/go v0.4.0
+       github.com/vishvananda/netlink v0.0.0-20210811191823-e1a867c6b452
+       github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae
+       golang.org/x/net v0.0.0-20211209124913-491a49abca63
+       golang.org/x/sys v0.0.0-20211205182925-97ca703d548d
+       google.golang.org/grpc v1.32.0
+)
+
+require (
+       github.com/BurntSushi/toml v0.4.1 // indirect
+       github.com/google/go-cmp v0.5.6 // indirect
+       github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 // indirect
+       github.com/mdlayher/netlink v1.4.2 // indirect
+       github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb // indirect
+       golang.org/x/mod v0.5.1 // indirect
+       golang.org/x/text v0.3.7 // indirect
+       golang.org/x/tools v0.1.8 // indirect
+       golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
+       google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect
+       google.golang.org/protobuf v1.26.0 // indirect
+       honnef.co/go/tools v0.2.2 // indirect
+)
diff --git a/daemon/go.sum b/daemon/go.sum
new file mode 100644 (file)
index 0000000..9b14c3b
--- /dev/null
@@ -0,0 +1,197 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
+github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cilium/ebpf v0.5.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
+github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gopacket v1.1.14 h1:1+TEhSu8Mh154ZBVjyd1Nt2Bb7cnyOeE3GQyb1WGLqI=
+github.com/google/gopacket v1.1.14/go.mod h1:UCLx9mCmAwsVbn6qQl1WIEt2SO7Nd2fD0th1TBAsqBw=
+github.com/google/nftables v0.1.0 h1:T6lS4qudrMufcNIZ8wSRrL+iuwhsKxpN+zFLxhUWOqk=
+github.com/google/nftables v0.1.0/go.mod h1:b97ulCCFipUC+kSin+zygkvUVpx0vyIAwxXFdY3PlNc=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/iovisor/gobpf v0.2.0 h1:34xkQxft+35GagXBk3n23eqhm0v7q0ejeVirb8sqEOQ=
+github.com/iovisor/gobpf v0.2.0/go.mod h1:WSY9Jj5RhdgC3ci1QaacvbFdQ8cbrEjrpiZbLHLt2s4=
+github.com/josharian/native v0.0.0-20200817173448-b6b71def0850 h1:uhL5Gw7BINiiPAo24A2sxkcDI0Jt/sqp1v5xQCniEFA=
+github.com/josharian/native v0.0.0-20200817173448-b6b71def0850/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
+github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw=
+github.com/jsimonetti/rtnetlink v0.0.0-20200117123717-f846d4f6c1f4/go.mod h1:WGuG/smIU4J/54PblvSbh+xvCZmpJnFgr3ds6Z55XMQ=
+github.com/jsimonetti/rtnetlink v0.0.0-20201009170750-9c6f07d100c1/go.mod h1:hqoO/u39cqLeBLebZ8fWdE96O7FxrAsRYhnVOdgHxok=
+github.com/jsimonetti/rtnetlink v0.0.0-20201216134343-bde56ed16391/go.mod h1:cR77jAZG3Y3bsb8hF6fHJbFoyFukLFOkQ98S0pQz3xw=
+github.com/jsimonetti/rtnetlink v0.0.0-20201220180245-69540ac93943/go.mod h1:z4c53zj6Eex712ROyh8WI0ihysb5j2ROyV42iNogmAs=
+github.com/jsimonetti/rtnetlink v0.0.0-20210122163228-8d122574c736/go.mod h1:ZXpIyOK59ZnN7J0BV99cZUPmsqDRZ3eq5X+st7u/oSA=
+github.com/jsimonetti/rtnetlink v0.0.0-20210212075122-66c871082f2b/go.mod h1:8w9Rh8m+aHZIG69YPGGem1i5VzoyRC8nw2kA8B+ik5U=
+github.com/jsimonetti/rtnetlink v0.0.0-20210525051524-4cc836578190/go.mod h1:NmKSdU4VGSiv1bMsdqNALI4RSvvjtz65tTMCnD05qLo=
+github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786 h1:N527AHMa793TP5z5GNAn/VLPzlc0ewzWdeP/25gDfgQ=
+github.com/jsimonetti/rtnetlink v0.0.0-20211022192332-93da33804786/go.mod h1:v4hqbTdfQngbVSZJVWUhGE/lbTFf9jb+ygmNUDQMuOs=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/mdlayher/ethtool v0.0.0-20210210192532-2b88debcdd43/go.mod h1:+t7E0lkKfbBsebllff1xdTmyJt8lH37niI6kwFk9OTo=
+github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60 h1:tHdB+hQRHU10CfcK0furo6rSNgZ38JT8uPh70c/pFD8=
+github.com/mdlayher/ethtool v0.0.0-20211028163843-288d040e9d60/go.mod h1:aYbhishWc4Ai3I2U4Gaa2n3kHWSwzme6EsG/46HRQbE=
+github.com/mdlayher/genetlink v1.0.0 h1:OoHN1OdyEIkScEmRgxLEe2M9U8ClMytqA5niynLtfj0=
+github.com/mdlayher/genetlink v1.0.0/go.mod h1:0rJ0h4itni50A86M2kHcgS85ttZazNt7a8H2a2cw0Gc=
+github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE4aiYnlUsyGGCOpPETfdQq4Jhsgf1fk3cwQaA=
+github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M=
+github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY=
+github.com/mdlayher/netlink v1.1.1/go.mod h1:WTYpFb/WTvlRJAyKhZL5/uy69TDDpHHu2VZmb2XgV7o=
+github.com/mdlayher/netlink v1.2.0/go.mod h1:kwVW1io0AZy9A1E2YYgaD4Cj+C+GPkU6klXCMzIJ9p8=
+github.com/mdlayher/netlink v1.2.1/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
+github.com/mdlayher/netlink v1.2.2-0.20210123213345-5cc92139ae3e/go.mod h1:bacnNlfhqHqqLo4WsYeXSqfyXkInQ9JneWI68v1KwSU=
+github.com/mdlayher/netlink v1.3.0/go.mod h1:xK/BssKuwcRXHrtN04UBkwQ6dY9VviGGuriDdoPSWys=
+github.com/mdlayher/netlink v1.4.0/go.mod h1:dRJi5IABcZpBD2A3D0Mv/AiX8I9uDEu5oGkAVrekmf8=
+github.com/mdlayher/netlink v1.4.1/go.mod h1:e4/KuJ+s8UhfUpO9z00/fDZZmhSrs+oxyqAS9cNgn6Q=
+github.com/mdlayher/netlink v1.4.2 h1:3sbnJWe/LETovA7yRZIX3f9McVOWV3OySH6iIBxiFfI=
+github.com/mdlayher/netlink v1.4.2/go.mod h1:13VaingaArGUTUxFLf/iEovKxXji32JAtF858jZYEug=
+github.com/mdlayher/socket v0.0.0-20210307095302-262dc9984e00/go.mod h1:GAFlyu4/XV68LkQKYzKhIo/WW7j3Zi0YRAz/BOoanUc=
+github.com/mdlayher/socket v0.0.0-20211007213009-516dcbdf0267/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
+github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb h1:2dC7L10LmTqlyMVzFJ00qM25lqESg9Z4u3GuEXN5iHY=
+github.com/mdlayher/socket v0.0.0-20211102153432-57e3fa563ecb/go.mod h1:nFZ1EtZYK8Gi/k6QNu7z7CgO20i/4ExeQswwWuPmG/g=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/varlink/go v0.4.0 h1:+/BQoUO9eJK/+MTSHwFcJch7TMsb6N6Dqp6g0qaXXRo=
+github.com/varlink/go v0.4.0/go.mod h1:DKg9Y2ctoNkesREGAEak58l+jOC6JU2aqZvUYs5DynU=
+github.com/vishvananda/netlink v0.0.0-20210811191823-e1a867c6b452 h1:xe1bLd/sNkKVWdZuAb2+4JeMQMYyQ7Av38iRrE1lhm8=
+github.com/vishvananda/netlink v0.0.0-20210811191823-e1a867c6b452/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
+github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
+github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae h1:4hwBBUfQCFe3Cym0ZtKyq7L16eZUtYKs+BaHDN6mAns=
+github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
+golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201216054612-986b41b23924/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20210928044308-7d9f5e0b762b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211020060615-d418f374d309/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211201190559-0a0e4e1bb54c/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20211209124913-491a49abca63 h1:iocB37TsdFuN6IBRZ+ry36wrkoV51/tl5vOWqkcPGvY=
+golang.org/x/net v0.0.0-20211209124913-491a49abca63/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201009025420-dfb3f7c4e634/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201118182958-a01c418693c7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201218084310-7d0127a74742/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210123111255-9b0068b26619/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210216163648-f7da38b97c65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211205182925-97ca703d548d h1:FjkYO/PPp4Wi0EAUOVLxePm7qVW4r4ctbWpURyuOD0E=
+golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
+golang.org/x/tools v0.1.8 h1:P1HhGGuLW4aAclzjtmJdf0mJOjVUZUzOTqkAkWL+l6w=
+golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
+google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
+honnef.co/go/tools v0.2.2 h1:MNh1AVMyVX23VUHE2O27jm6lNj3vjO5DexS4A1xvnzk=
+honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
diff --git a/daemon/log/formats/csv.go b/daemon/log/formats/csv.go
new file mode 100644 (file)
index 0000000..3e3d832
--- /dev/null
@@ -0,0 +1,50 @@
+package formats
+
+import (
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// CSV name of the output format, used in json configs
+const CSV = "csv"
+
+// Csv object
+type Csv struct {
+}
+
+// NewCSV returns a new CSV transformer object.
+func NewCSV() *Csv {
+       return &Csv{}
+}
+
+// Transform takes input arguments and formats them to CSV.
+func (c *Csv) Transform(args ...interface{}) (out string) {
+       p := args[0]
+       values := p.([]interface{})
+       for _, val := range values {
+               switch val.(type) {
+               case *protocol.Connection:
+                       con := val.(*protocol.Connection)
+                       out = fmt.Sprint(out,
+                               con.SrcIp, ",",
+                               con.SrcPort, ",",
+                               con.DstIp, ",",
+                               con.DstHost, ",",
+                               con.DstPort, ",",
+                               con.Protocol, ",",
+                               con.ProcessId, ",",
+                               con.UserId, ",",
+                               //con.ProcessComm, ",",
+                               con.ProcessPath, ",",
+                               con.ProcessArgs, ",",
+                               con.ProcessCwd, ",",
+                       )
+               default:
+                       out = fmt.Sprint(out, val, ",")
+               }
+       }
+       out = out[:len(out)-1]
+
+       return
+}
diff --git a/daemon/log/formats/formats.go b/daemon/log/formats/formats.go
new file mode 100644 (file)
index 0000000..efb3090
--- /dev/null
@@ -0,0 +1,9 @@
+package formats
+
+// LoggerFormat is the common interface that every format must meet.
+// Transform expects an arbitrary number of arguments and types, and
+// it must transform them to a string.
+// Arguments can be of type Connection, string, int, etc.
+type LoggerFormat interface {
+       Transform(...interface{}) string
+}
diff --git a/daemon/log/formats/json.go b/daemon/log/formats/json.go
new file mode 100644 (file)
index 0000000..438c032
--- /dev/null
@@ -0,0 +1,69 @@
+package formats
+
+import (
+       "encoding/json"
+       "fmt"
+
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// JSON name of the output format, used in our json config
+const JSON = "json"
+
+// events types
+const (
+       EvConnection = iota
+       EvExec
+)
+
+// JSONEventFormat object to be sent to the remote service.
+// TODO: Expand as needed: ebpf events, etc.
+type JSONEventFormat struct {
+       Event  interface{} `json:"Event"`
+       Rule   string      `json:"Rule"`
+       Action string      `json:"Action"`
+       Type   uint8       `json:"Type"`
+}
+
+// NewJSON returns a new Json format, to send events as json.
+// The json is the protobuffer in json format.
+func NewJSON() *JSONEventFormat {
+       return &JSONEventFormat{}
+}
+
+// Transform takes input arguments and formats them to JSON format.
+func (j *JSONEventFormat) Transform(args ...interface{}) (out string) {
+       p := args[0]
+       jObj := &JSONEventFormat{}
+
+       values := p.([]interface{})
+       for n, val := range values {
+               switch val.(type) {
+               // TODO:
+               // case *protocol.Rule:
+               // case *protocol.Process:
+               // case *protocol.Alerts:
+               case *protocol.Connection:
+                       // XXX: All fields of the Connection object are sent, is this what we want?
+                       // or should we send an anonymous json?
+                       jObj.Event = val.(*protocol.Connection)
+                       jObj.Type = EvConnection
+
+               case string:
+                       // action
+                       // rule name
+                       if n == 1 {
+                               jObj.Action = val.(string)
+                       } else if n == 2 {
+                               jObj.Rule = val.(string)
+                       }
+               }
+       }
+
+       rawCfg, err := json.Marshal(&jObj)
+       if err != nil {
+               return
+       }
+       out = fmt.Sprint(string(rawCfg), "\n\n")
+       return
+}
diff --git a/daemon/log/formats/rfc3164.go b/daemon/log/formats/rfc3164.go
new file mode 100644 (file)
index 0000000..7138bac
--- /dev/null
@@ -0,0 +1,68 @@
+package formats
+
+import (
+       "fmt"
+       "log/syslog"
+       "os"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// RFC3164 name of the output format, used in our json config
+const RFC3164 = "rfc3164"
+
+// Rfc3164 object
+type Rfc3164 struct {
+       seq int
+}
+
+// NewRfc3164 returns a new Rfc3164 object, that transforms a message to
+// RFC3164 format.
+func NewRfc3164() *Rfc3164 {
+       return &Rfc3164{}
+}
+
+// Transform takes input arguments and formats them to RFC3164 format.
+func (r *Rfc3164) Transform(args ...interface{}) (out string) {
+       hostname := ""
+       tag := ""
+       arg1 := args[0]
+       // we can do this better. Think.
+       if len(args) > 1 {
+               hostname = args[1].(string)
+               tag = args[2].(string)
+       }
+       values := arg1.([]interface{})
+       for n, val := range values {
+               switch val.(type) {
+               case *protocol.Connection:
+                       con := val.(*protocol.Connection)
+                       out = fmt.Sprint(out,
+                               " SRC=\"", con.SrcIp, "\"",
+                               " SPT=\"", con.SrcPort, "\"",
+                               " DST=\"", con.DstIp, "\"",
+                               " DSTHOST=\"", con.DstHost, "\"",
+                               " DPT=\"", con.DstPort, "\"",
+                               " PROTO=\"", con.Protocol, "\"",
+                               " PID=\"", con.ProcessId, "\"",
+                               " UID=\"", con.UserId, "\"",
+                               //" COMM=", con.ProcessComm, "\"",
+                               " PATH=\"", con.ProcessPath, "\"",
+                               " CMDLINE=\"", con.ProcessArgs, "\"",
+                               " CWD=\"", con.ProcessCwd, "\"",
+                       )
+               default:
+                       out = fmt.Sprint(out, " ARG", n, "=\"", val, "\"")
+               }
+       }
+       out = fmt.Sprintf("<%d>%s %s %s[%d]: [%s]\n",
+               syslog.LOG_NOTICE|syslog.LOG_DAEMON,
+               time.Now().Format(time.RFC3339),
+               hostname,
+               tag,
+               os.Getpid(),
+               out[1:])
+
+       return
+}
diff --git a/daemon/log/formats/rfc5424.go b/daemon/log/formats/rfc5424.go
new file mode 100644 (file)
index 0000000..40e8c91
--- /dev/null
@@ -0,0 +1,69 @@
+package formats
+
+import (
+       "fmt"
+       "log/syslog"
+       "os"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// RFC5424 name of the output format, used in our json config
+const RFC5424 = "rfc5424"
+
+// Rfc5424 object
+type Rfc5424 struct {
+       seq int
+}
+
+// NewRfc5424 returns a new Rfc5424 object, that transforms a message to
+// RFC5424 format (sort of).
+func NewRfc5424() *Rfc5424 {
+       return &Rfc5424{}
+}
+
+// Transform takes input arguments and formats them to RFC5424 format.
+func (r *Rfc5424) Transform(args ...interface{}) (out string) {
+       hostname := ""
+       tag := ""
+       arg1 := args[0]
+       if len(args) > 1 {
+               arg2 := args[1]
+               arg3 := args[2]
+               hostname = arg2.(string)
+               tag = arg3.(string)
+       }
+       values := arg1.([]interface{})
+       for n, val := range values {
+               switch val.(type) {
+               case *protocol.Connection:
+                       con := val.(*protocol.Connection)
+                       out = fmt.Sprint(out,
+                               " SRC=\"", con.SrcIp, "\"",
+                               " SPT=\"", con.SrcPort, "\"",
+                               " DST=\"", con.DstIp, "\"",
+                               " DSTHOST=\"", con.DstHost, "\"",
+                               " DPT=\"", con.DstPort, "\"",
+                               " PROTO=\"", con.Protocol, "\"",
+                               " PID=\"", con.ProcessId, "\"",
+                               " UID=\"", con.UserId, "\"",
+                               //" COMM=", con.ProcessComm, "\"",
+                               " PATH=\"", con.ProcessPath, "\"",
+                               " CMDLINE=\"", con.ProcessArgs, "\"",
+                               " CWD=\"", con.ProcessCwd, "\"",
+                       )
+               default:
+                       out = fmt.Sprint(out, " ARG", n, "=\"", val, "\"")
+               }
+       }
+       out = fmt.Sprintf("<%d>1 %s %s %s %d TCPOUT - [%s]\n",
+               syslog.LOG_NOTICE|syslog.LOG_DAEMON,
+               time.Now().Format(time.RFC3339),
+               hostname,
+               tag,
+               os.Getpid(),
+               out[1:])
+
+       return
+}
diff --git a/daemon/log/log.go b/daemon/log/log.go
new file mode 100644 (file)
index 0000000..0602124
--- /dev/null
@@ -0,0 +1,253 @@
+package log
+
+import (
+       "fmt"
+       "os"
+       "strings"
+       "sync"
+       "time"
+)
+
+type Handler func(format string, args ...interface{})
+
+// https://misc.flogisoft.com/bash/tip_colors_and_formatting
+const (
+       BOLD = "\033[1m"
+       DIM  = "\033[2m"
+
+       RED    = "\033[31m"
+       GREEN  = "\033[32m"
+       BLUE   = "\033[34m"
+       YELLOW = "\033[33m"
+
+       FG_BLACK = "\033[30m"
+       FG_WHITE = "\033[97m"
+
+       BG_DGRAY  = "\033[100m"
+       BG_RED    = "\033[41m"
+       BG_GREEN  = "\033[42m"
+       BG_YELLOW = "\033[43m"
+       BG_LBLUE  = "\033[104m"
+
+       RESET = "\033[0m"
+)
+
+// log level constants
+const (
+       DEBUG = iota
+       INFO
+       IMPORTANT
+       WARNING
+       ERROR
+       FATAL
+)
+
+//
+var (
+       WithColors = true
+       Output     = os.Stdout
+       StdoutFile = "/dev/stdout"
+       DateFormat = "2006-01-02 15:04:05"
+       MinLevel   = INFO
+       LogUTC     = true
+       LogMicro   = false
+
+       mutex  = &sync.RWMutex{}
+       labels = map[int]string{
+               DEBUG:     "DBG",
+               INFO:      "INF",
+               IMPORTANT: "IMP",
+               WARNING:   "WAR",
+               ERROR:     "ERR",
+               FATAL:     "!!!",
+       }
+       colors = map[int]string{
+               DEBUG:     DIM + FG_BLACK + BG_DGRAY,
+               INFO:      FG_WHITE + BG_GREEN,
+               IMPORTANT: FG_WHITE + BG_LBLUE,
+               WARNING:   FG_WHITE + BG_YELLOW,
+               ERROR:     FG_WHITE + BG_RED,
+               FATAL:     FG_WHITE + BG_RED + BOLD,
+       }
+)
+
+// Wrap wraps a text with effects
+func Wrap(s, effect string) string {
+       if WithColors == true {
+               s = effect + s + RESET
+       }
+       return s
+}
+
+// Dim dims a text
+func Dim(s string) string {
+       return Wrap(s, DIM)
+}
+
+// Bold bolds a text
+func Bold(s string) string {
+       return Wrap(s, BOLD)
+}
+
+// Red reds the text
+func Red(s string) string {
+       return Wrap(s, RED)
+}
+
+// Green greens the text
+func Green(s string) string {
+       return Wrap(s, GREEN)
+}
+
+// Blue blues the text
+func Blue(s string) string {
+       return Wrap(s, BLUE)
+}
+
+// Yellow yellows the text
+func Yellow(s string) string {
+       return Wrap(s, YELLOW)
+}
+
+// Raw prints out a text without colors
+func Raw(format string, args ...interface{}) {
+       mutex.RLock()
+       defer mutex.RUnlock()
+       fmt.Fprintf(Output, format, args...)
+}
+
+// SetLogLevel sets the log level
+func SetLogLevel(newLevel int) {
+       mutex.Lock()
+       defer mutex.Unlock()
+       MinLevel = newLevel
+}
+
+// GetLogLevel returns the current log level configured.
+func GetLogLevel() int {
+       mutex.RLock()
+       defer mutex.RUnlock()
+
+       return MinLevel
+}
+
+// SetLogUTC configures UTC timestamps
+func SetLogUTC(newLogUTC bool) {
+       mutex.Lock()
+       defer mutex.Unlock()
+       LogUTC = newLogUTC
+}
+
+// GetLogUTC returns the current config.
+func GetLogUTC() bool {
+       mutex.RLock()
+       defer mutex.RUnlock()
+
+       return LogUTC
+}
+
+// SetLogMicro configures microsecond timestamps
+func SetLogMicro(newLogMicro bool) {
+       mutex.Lock()
+       defer mutex.Unlock()
+       LogMicro = newLogMicro
+}
+
+// GetLogMicro returns the current config.
+func GetLogMicro() bool {
+       mutex.Lock()
+       defer mutex.Unlock()
+
+       return LogMicro
+}
+
+// Log prints out a text with the given color and format
+func Log(level int, format string, args ...interface{}) {
+       mutex.Lock()
+       defer mutex.Unlock()
+       if level >= MinLevel {
+               label := labels[level]
+               color := colors[level]
+
+               datefmt := DateFormat
+
+               if LogMicro == true {
+                       datefmt = DateFormat + ".000000"
+               }
+               when := time.Now().UTC().Format(datefmt)
+               if LogUTC == false {
+                       when = time.Now().Local().Format(datefmt)
+               }
+
+               what := fmt.Sprintf(format, args...)
+               if strings.HasSuffix(what, "\n") == false {
+                       what += "\n"
+               }
+
+               l := Dim("[%s]")
+               r := Wrap(" %s ", color) + " %s"
+
+               fmt.Fprintf(Output, l+" "+r, when, label, what)
+       }
+}
+
+func setDefaultLogOutput() {
+       mutex.Lock()
+       Output = os.Stdout
+       mutex.Unlock()
+}
+
+// OpenFile opens a file to print out the logs
+func OpenFile(logFile string) (err error) {
+       if logFile == StdoutFile {
+               setDefaultLogOutput()
+               return
+       }
+
+       if Output, err = os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644); err != nil {
+               Error("Error opening log: %s %s", logFile, err)
+               //fallback to stdout
+               setDefaultLogOutput()
+       }
+       Important("Start writing logs to %s", logFile)
+
+       return err
+}
+
+// Close closes the current output file descriptor
+func Close() {
+       if Output != os.Stdout {
+               Output.Close()
+       }
+}
+
+// Debug is the log level for debugging purposes
+func Debug(format string, args ...interface{}) {
+       Log(DEBUG, format, args...)
+}
+
+// Info is the log level for informative messages
+func Info(format string, args ...interface{}) {
+       Log(INFO, format, args...)
+}
+
+// Important is the log level for things that must pay attention
+func Important(format string, args ...interface{}) {
+       Log(IMPORTANT, format, args...)
+}
+
+// Warning is the log level for non-critical errors
+func Warning(format string, args ...interface{}) {
+       Log(WARNING, format, args...)
+}
+
+// Error is the log level for errors that should be corrected
+func Error(format string, args ...interface{}) {
+       Log(ERROR, format, args...)
+}
+
+// Fatal is the log level for errors that must be corrected before continue
+func Fatal(format string, args ...interface{}) {
+       Log(FATAL, format, args...)
+       os.Exit(1)
+}
diff --git a/daemon/log/loggers/logger.go b/daemon/log/loggers/logger.go
new file mode 100644 (file)
index 0000000..4fc81e1
--- /dev/null
@@ -0,0 +1,106 @@
+package loggers
+
+import "fmt"
+
+const logTag = "opensnitch"
+
+// Logger is the common interface that every logger must met.
+// Serves as a generic holder of different types of loggers.
+type Logger interface {
+       Transform(...interface{}) string
+       Write(string)
+}
+
+// LoggerConfig holds the configuration of a logger
+type LoggerConfig struct {
+       // Name of the logger: syslog, elastic, ...
+       Name string
+       // Format: rfc5424, csv, json, ...
+       Format string
+       // Protocol: udp, tcp
+       Protocol string
+       // Server: 127.0.0.1:514
+       Server string
+       // WriteTimeout:
+       WriteTimeout string
+       // Tag: opensnitchd, mytag, ...
+       Tag string
+       // Workers: number of workers
+       Workers int
+}
+
+// LoggerManager represents the LoggerManager.
+type LoggerManager struct {
+       loggers map[string]Logger
+       msgs    chan []interface{}
+       count   int
+}
+
+// NewLoggerManager instantiates all the configured loggers.
+func NewLoggerManager() *LoggerManager {
+       lm := &LoggerManager{
+               loggers: make(map[string]Logger),
+       }
+
+       return lm
+}
+
+// Load loggers configuration and initialize them.
+func (l *LoggerManager) Load(configs []LoggerConfig, workers int) {
+       for _, cfg := range configs {
+               switch cfg.Name {
+               case LOGGER_REMOTE:
+                       if lgr, err := NewRemote(&cfg); err == nil {
+                               l.count++
+                               l.loggers[fmt.Sprint(lgr.Name, lgr.cfg.Server, lgr.cfg.Protocol)] = lgr
+                               workers += cfg.Workers
+                       }
+               case LOGGER_REMOTE_SYSLOG:
+                       if lgr, err := NewRemoteSyslog(&cfg); err == nil {
+                               l.count++
+                               l.loggers[fmt.Sprint(lgr.Name, lgr.cfg.Server, lgr.cfg.Protocol)] = lgr
+                               workers += cfg.Workers
+                       }
+               case LOGGER_SYSLOG:
+                       if lgr, err := NewSyslog(&cfg); err == nil {
+                               l.count++
+                               l.loggers[lgr.Name] = lgr
+                               workers += cfg.Workers
+                       }
+               }
+       }
+
+       if workers == 0 {
+               workers = 4
+       }
+
+       l.msgs = make(chan []interface{}, workers)
+       for i := 0; i < workers; i++ {
+               go newWorker(i, l)
+       }
+
+}
+
+func (l *LoggerManager) write(args ...interface{}) {
+       for _, logger := range l.loggers {
+               logger.Write(logger.Transform(args...))
+       }
+}
+
+func newWorker(id int, l *LoggerManager) {
+       for {
+               for msg := range l.msgs {
+                       l.write(msg)
+               }
+       }
+}
+
+// Log sends data to the loggers.
+func (l *LoggerManager) Log(args ...interface{}) {
+       if l.count > 0 {
+               go func(args ...interface{}) {
+                       argv := args
+                       l.msgs <- argv
+               }(args...)
+       }
+}
diff --git a/daemon/log/loggers/remote.go b/daemon/log/loggers/remote.go
new file mode 100644 (file)
index 0000000..7936c2f
--- /dev/null
@@ -0,0 +1,184 @@
+package loggers
+
+import (
+       "fmt"
+       "log/syslog"
+       "net"
+       "os"
+       "strings"
+       "sync"
+       "sync/atomic"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/formats"
+)
+
+const (
+       LOGGER_REMOTE = "remote"
+)
+
+// Remote defines the logger that writes events to a generic remote server.
+// It can write to the local or a remote daemon, UDP or TCP.
+// It supports writing events in RFC5424, RFC3164, CSV and JSON formats.
+type Remote struct {
+       Name     string
+       Tag      string
+       Hostname string
+
+       Writer    *syslog.Writer
+       logFormat formats.LoggerFormat
+       cfg       *LoggerConfig
+       netConn   net.Conn
+       Timeout   time.Duration
+       errors    uint32
+       maxErrors uint32
+       status    uint32
+
+       mu *sync.RWMutex
+}
+
+// NewRemote returns a new object that manipulates and prints outbound connections
+// to a remote syslog server, with the given format (RFC5424 by default)
+func NewRemote(cfg *LoggerConfig) (*Remote, error) {
+       var err error
+       log.Info("NewRemote logger: %v", cfg)
+
+       sys := &Remote{
+               mu: &sync.RWMutex{},
+       }
+       sys.Name = LOGGER_REMOTE
+       sys.cfg = cfg
+
+       // list of allowed formats for this logger
+       sys.logFormat = formats.NewRfc5424()
+       if cfg.Format == formats.RFC3164 {
+               sys.logFormat = formats.NewRfc3164()
+       } else if cfg.Format == formats.JSON {
+               sys.logFormat = formats.NewJSON()
+       } else if cfg.Format == formats.CSV {
+               sys.logFormat = formats.NewCSV()
+       }
+
+       sys.Tag = logTag
+       if cfg.Tag != "" {
+               sys.Tag = cfg.Tag
+       }
+       sys.Hostname, err = os.Hostname()
+       if err != nil {
+               sys.Hostname = "localhost"
+       }
+       if cfg.WriteTimeout == "" {
+               cfg.WriteTimeout = writeTimeout
+       }
+       sys.Timeout = (time.Second * 15)
+
+       if err = sys.Open(); err != nil {
+               log.Error("Error loading logger: %s", err)
+               return nil, err
+       }
+       log.Info("[%s] initialized: %v", sys.Name, cfg)
+
+       return sys, err
+}
+
+// Open opens a new connection with a server or with the daemon.
+func (s *Remote) Open() (err error) {
+       atomic.StoreUint32(&s.errors, 0)
+       if s.cfg.Server == "" {
+               return fmt.Errorf("[%s] Server address must not be empty", s.Name)
+       }
+       s.mu.Lock()
+       s.netConn, err = s.Dial(s.cfg.Protocol, s.cfg.Server, s.Timeout*5)
+       s.mu.Unlock()
+
+       if err == nil {
+               atomic.StoreUint32(&s.status, CONNECTED)
+       }
+       return err
+}
+
+// Dial opens a new connection with a remote server.
+func (s *Remote) Dial(proto, addr string, connTimeout time.Duration) (netConn net.Conn, err error) {
+       switch proto {
+       case "udp", "tcp":
+               netConn, err = net.DialTimeout(proto, addr, connTimeout)
+               if err != nil {
+                       return nil, err
+               }
+       default:
+               return nil, fmt.Errorf("[%s] Network protocol %s not supported", s.Name, proto)
+       }
+
+       return netConn, nil
+}
+
+// Close closes the writer object
+func (s *Remote) Close() (err error) {
+       s.mu.RLock()
+       if s.netConn != nil {
+               err = s.netConn.Close()
+               //s.netConn.conn = nil
+       }
+       s.mu.RUnlock()
+       atomic.StoreUint32(&s.status, DISCONNECTED)
+       return
+}
+
+// ReOpen tries to reestablish the connection with the writer
+func (s *Remote) ReOpen() {
+       if atomic.LoadUint32(&s.status) == CONNECTING {
+               return
+       }
+       atomic.StoreUint32(&s.status, CONNECTING)
+       if err := s.Close(); err != nil {
+               log.Debug("[%s] error closing Close(): %s", s.Name, err)
+       }
+
+       if err := s.Open(); err != nil {
+               log.Debug("[%s] ReOpen() error: %s", s.Name, err)
+       } else {
+               log.Debug("[%s] ReOpen() ok", s.Name)
+       }
+}
+
+// Transform transforms data for proper ingestion.
+func (s *Remote) Transform(args ...interface{}) (out string) {
+       if s.logFormat != nil {
+               args = append(args, s.Hostname)
+               args = append(args, s.Tag)
+               out = s.logFormat.Transform(args...)
+       }
+       return
+}
+
+func (s *Remote) Write(msg string) {
+       deadline := time.Now().Add(s.Timeout)
+
+       // BUG: it's fairly common to have write timeouts via udp/tcp.
+       // Reopening the connection with the server helps to resume sending events to the server,
+       // and have a continuous stream of events. Otherwise it'd stop working.
+       // I haven't figured out yet why these write errors ocurr.
+       s.mu.Lock()
+       s.netConn.SetWriteDeadline(deadline)
+       _, err := s.netConn.Write([]byte(msg))
+       s.mu.Unlock()
+       if err == nil {
+               return
+       }
+
+       log.Debug("[%s] %s write error: %v", s.Name, s.cfg.Protocol, err.(net.Error))
+       atomic.AddUint32(&s.errors, 1)
+       if atomic.LoadUint32(&s.errors) > maxAllowedErrors {
+               s.ReOpen()
+               return
+       }
+}
+
+func (s *Remote) formatLine(msg string) string {
+       nl := ""
+       if !strings.HasSuffix(msg, "\n") {
+               nl = "\n"
+       }
+       return fmt.Sprintf("%s%s", msg, nl)
+}
diff --git a/daemon/log/loggers/remote_syslog.go b/daemon/log/loggers/remote_syslog.go
new file mode 100644 (file)
index 0000000..e67b86e
--- /dev/null
@@ -0,0 +1,180 @@
+package loggers
+
+import (
+       "fmt"
+       "net"
+       "os"
+       "sync"
+       "sync/atomic"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/formats"
+)
+
+const (
+       LOGGER_REMOTE_SYSLOG = "remote_syslog"
+       writeTimeout         = "1s"
+       // restart syslog connection after these amount of errors
+       maxAllowedErrors = 10
+)
+
+// connection status
+const (
+       DISCONNECTED = iota
+       CONNECTED
+       CONNECTING
+)
+
+// RemoteSyslog defines the logger that writes traces to the syslog.
+// It can write to the local or a remote daemon.
+type RemoteSyslog struct {
+       Syslog
+
+       Hostname string
+       netConn  net.Conn
+       Timeout  time.Duration
+       errors   uint32
+       status   uint32
+
+       mu *sync.RWMutex
+}
+
+// NewRemoteSyslog returns a new object that manipulates and prints outbound connections
+// to a remote syslog server, with the given format (RFC5424 by default)
+func NewRemoteSyslog(cfg *LoggerConfig) (*RemoteSyslog, error) {
+       var err error
+       log.Info("NewSyslog logger: %v", cfg)
+
+       sys := &RemoteSyslog{
+               mu: &sync.RWMutex{},
+       }
+       sys.Name = LOGGER_REMOTE_SYSLOG
+       sys.cfg = cfg
+
+       // list of allowed formats for this logger
+       sys.logFormat = formats.NewRfc5424()
+       if cfg.Format == formats.RFC3164 {
+               sys.logFormat = formats.NewRfc3164()
+       } else if cfg.Format == formats.CSV {
+               sys.logFormat = formats.NewCSV()
+       }
+
+       sys.Tag = logTag
+       if cfg.Tag != "" {
+               sys.Tag = cfg.Tag
+       }
+       sys.Hostname, err = os.Hostname()
+       if err != nil {
+               sys.Hostname = "localhost"
+       }
+       if cfg.WriteTimeout == "" {
+               cfg.WriteTimeout = writeTimeout
+       }
+       sys.Timeout, _ = time.ParseDuration(cfg.WriteTimeout)
+
+       if err = sys.Open(); err != nil {
+               log.Error("Error loading logger: %s", err)
+               return nil, err
+       }
+       log.Info("[%s] initialized: %v", sys.Name, cfg)
+
+       return sys, err
+}
+
+// Open opens a new connection with a server or with the daemon.
+func (s *RemoteSyslog) Open() (err error) {
+       atomic.StoreUint32(&s.errors, 0)
+       if s.cfg.Server == "" {
+               return fmt.Errorf("[%s] Server address must not be empty", s.Name)
+       }
+       s.mu.Lock()
+       s.netConn, err = s.Dial(s.cfg.Protocol, s.cfg.Server, s.Timeout*5)
+       s.mu.Unlock()
+
+       if err == nil {
+               atomic.StoreUint32(&s.status, CONNECTED)
+       }
+       return err
+}
+
+// Dial opens a new connection with a syslog server.
+func (s *RemoteSyslog) Dial(proto, addr string, connTimeout time.Duration) (netConn net.Conn, err error) {
+       switch proto {
+       case "udp", "tcp":
+               netConn, err = net.DialTimeout(proto, addr, connTimeout)
+               if err != nil {
+                       return nil, err
+               }
+       default:
+               return nil, fmt.Errorf("[%s] Network protocol %s not supported", s.Name, proto)
+       }
+
+       return netConn, nil
+}
+
+// Close closes the writer object
+func (s *RemoteSyslog) Close() (err error) {
+       s.mu.RLock()
+       defer s.mu.RUnlock()
+
+       if s.netConn != nil {
+               err = s.netConn.Close()
+               //s.netConn.conn = nil
+       }
+       atomic.StoreUint32(&s.status, DISCONNECTED)
+       return
+}
+
+// ReOpen tries to reestablish the connection with the writer
+func (s *RemoteSyslog) ReOpen() {
+       if atomic.LoadUint32(&s.status) == CONNECTING {
+               return
+       }
+       atomic.StoreUint32(&s.status, CONNECTING)
+       if err := s.Close(); err != nil {
+               log.Debug("[%s] error closing Close(): %s", s.Name, err)
+       }
+
+       if err := s.Open(); err != nil {
+               log.Debug("[%s] ReOpen() error: %s", s.Name, err)
+               return
+       }
+}
+
+// Transform transforms data for proper ingestion.
+func (s *RemoteSyslog) Transform(args ...interface{}) (out string) {
+       if s.logFormat != nil {
+               args = append(args, s.Hostname)
+               args = append(args, s.Tag)
+               out = s.logFormat.Transform(args...)
+       }
+       return
+}
+
+func (s *RemoteSyslog) Write(msg string) {
+       deadline := time.Now().Add(s.Timeout)
+
+       // BUG: it's fairly common to have write timeouts via udp/tcp.
+       // Reopening the connection with the server helps to resume sending events to syslog,
+       // and have a continuous stream of events. Otherwise it'd stop working.
+       // I haven't figured out yet why these write errors ocurr.
+       s.mu.RLock()
+       s.netConn.SetWriteDeadline(deadline)
+       _, err := s.netConn.Write([]byte(msg))
+       s.mu.RUnlock()
+
+       if err != nil {
+               log.Debug("[%s] %s write error: %v", s.Name, s.cfg.Protocol, err.(net.Error))
+               atomic.AddUint32(&s.errors, 1)
+               if atomic.LoadUint32(&s.errors) > maxAllowedErrors {
+                       s.ReOpen()
+                       return
+               }
+       }
+}
+
+// https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/log/syslog/syslog.go;l=286;drc=0a1a092c4b56a1d4033372fbd07924dad8cbb50b
+func (s *RemoteSyslog) formatLine(msg string) string {
+       return msg
+}
diff --git a/daemon/log/loggers/syslog.go b/daemon/log/loggers/syslog.go
new file mode 100644 (file)
index 0000000..d0ea929
--- /dev/null
@@ -0,0 +1,79 @@
+package loggers
+
+import (
+       "log/syslog"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/formats"
+)
+
+const (
+       LOGGER_SYSLOG = "syslog"
+)
+
+// Syslog defines the logger that writes traces to the syslog.
+// It can write to the local or a remote daemon.
+type Syslog struct {
+       Name      string
+       Writer    *syslog.Writer
+       Tag       string
+       logFormat formats.LoggerFormat
+       cfg       *LoggerConfig
+}
+
+// NewSyslog returns a new object that manipulates and prints outbound connections
+// to syslog (local or remote), with the given format (RFC5424 by default)
+func NewSyslog(cfg *LoggerConfig) (*Syslog, error) {
+       var err error
+       log.Info("NewSyslog logger: %v", cfg)
+
+       sys := &Syslog{
+               Name: LOGGER_SYSLOG,
+               cfg:  cfg,
+       }
+
+       sys.logFormat = formats.NewRfc5424()
+       if cfg.Format == formats.CSV {
+               sys.logFormat = formats.NewCSV()
+       }
+
+       sys.Tag = logTag
+       if cfg.Tag != "" {
+               sys.Tag = cfg.Tag
+       }
+
+       if err = sys.Open(); err != nil {
+               log.Error("Error loading logger: %s", err)
+               return nil, err
+       }
+       log.Info("[%s logger] initialized: %v", sys.Name, cfg)
+
+       return sys, err
+}
+
+// Open opens a new connection with a server or with the daemon.
+func (s *Syslog) Open() error {
+       var err error
+       s.Writer, err = syslog.New(syslog.LOG_NOTICE|syslog.LOG_DAEMON, logTag)
+
+       return err
+}
+
+// Close closes the writer object
+func (s *Syslog) Close() error {
+       return s.Writer.Close()
+}
+
+// Transform transforms data for proper ingestion.
+func (s *Syslog) Transform(args ...interface{}) (out string) {
+       if s.logFormat != nil {
+               out = s.logFormat.Transform(args...)
+       }
+       return
+}
+
+func (s *Syslog) Write(msg string) {
+       if err := s.Writer.Notice(msg); err != nil {
+               log.Error("[%s] write error: %s", s.Name, err)
+       }
+}
diff --git a/daemon/main.go b/daemon/main.go
new file mode 100644 (file)
index 0000000..fdad4cc
--- /dev/null
@@ -0,0 +1,619 @@
+/*   Copyright (C) 2018      Simone Margaritelli
+//                 2021      themighty1
+//                 2022      calesanz
+//                 2019-2022 Gustavo Iñiguez Goia
+//
+//   This file is part of OpenSnitch.
+//
+//   OpenSnitch is free software: you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   OpenSnitch is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with OpenSnitch.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package main
+
+import (
+       "bytes"
+       "context"
+       "flag"
+       "fmt"
+       "io/ioutil"
+       golog "log"
+       "net"
+       "os"
+       "os/signal"
+       "runtime"
+       "runtime/pprof"
+       "syscall"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/dns"
+       "github.com/evilsocket/opensnitch/daemon/dns/systemd"
+       "github.com/evilsocket/opensnitch/daemon/firewall"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/loggers"
+       "github.com/evilsocket/opensnitch/daemon/netfilter"
+       "github.com/evilsocket/opensnitch/daemon/netlink"
+       "github.com/evilsocket/opensnitch/daemon/procmon/ebpf"
+       "github.com/evilsocket/opensnitch/daemon/procmon/monitor"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/statistics"
+       "github.com/evilsocket/opensnitch/daemon/ui"
+       "github.com/evilsocket/opensnitch/daemon/ui/config"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+var (
+       showVersion       = false
+       checkRequirements = false
+       procmonMethod     = ""
+       logFile           = ""
+       logUTC            = true
+       logMicro          = false
+       rulesPath         = "/etc/opensnitchd/rules/"
+       configFile        = "/etc/opensnitchd/default-config.json"
+       ebpfModPath       = "" // /usr/lib/opensnitchd/ebpf
+       noLiveReload      = false
+       queueNum          = 0
+       repeatQueueNum    int //will be set later to queueNum + 1
+       workers           = 16
+       debug             = false
+       warning           = false
+       important         = false
+       errorlog          = false
+
+       uiSocket = ""
+       uiClient = (*ui.Client)(nil)
+
+       cpuProfile = ""
+       memProfile = ""
+
+       ctx           = (context.Context)(nil)
+       cancel        = (context.CancelFunc)(nil)
+       err           = (error)(nil)
+       rules         = (*rule.Loader)(nil)
+       stats         = (*statistics.Statistics)(nil)
+       queue         = (*netfilter.Queue)(nil)
+       repeatPktChan = (<-chan netfilter.Packet)(nil)
+       pktChan       = (<-chan netfilter.Packet)(nil)
+       wrkChan       = (chan netfilter.Packet)(nil)
+       sigChan       = (chan os.Signal)(nil)
+       exitChan      = (chan bool)(nil)
+       loggerMgr     *loggers.LoggerManager
+       resolvMonitor *systemd.ResolvedMonitor
+)
+
+func init() {
+       flag.BoolVar(&showVersion, "version", debug, "Show daemon version of this executable and exit.")
+       flag.BoolVar(&checkRequirements, "check-requirements", debug, "Check system requirements for incompatibilities.")
+
+       flag.StringVar(&procmonMethod, "process-monitor-method", procmonMethod, "How to search for processes path. Options: ftrace, audit (experimental), ebpf (experimental), proc (default)")
+       flag.StringVar(&uiSocket, "ui-socket", uiSocket, "Path the UI gRPC service listener (https://github.com/grpc/grpc/blob/master/doc/naming.md).")
+       flag.IntVar(&queueNum, "queue-num", queueNum, "Netfilter queue number.")
+       flag.IntVar(&workers, "workers", workers, "Number of concurrent workers.")
+       flag.BoolVar(&noLiveReload, "no-live-reload", debug, "Disable rules live reloading.")
+
+       flag.StringVar(&rulesPath, "rules-path", rulesPath, "Path to load JSON rules from.")
+       flag.StringVar(&configFile, "config-file", configFile, "Path to the daemon configuration file.")
+       //flag.StringVar(&ebpfModPath, "ebpf-modules-path", ebpfModPath, "Path to the directory with the eBPF modules.")
+       flag.StringVar(&logFile, "log-file", logFile, "Write logs to this file instead of the standard output.")
+       flag.BoolVar(&logUTC, "log-utc", logUTC, "Write logs output with UTC timezone (enabled by default).")
+       flag.BoolVar(&logMicro, "log-micro", logMicro, "Write logs output with microsecond timestamp (disabled by default).")
+       flag.BoolVar(&debug, "debug", debug, "Enable debug level logs.")
+       flag.BoolVar(&warning, "warning", warning, "Enable warning level logs.")
+       flag.BoolVar(&important, "important", important, "Enable important level logs.")
+       flag.BoolVar(&errorlog, "error", errorlog, "Enable error level logs.")
+
+       flag.StringVar(&cpuProfile, "cpu-profile", cpuProfile, "Write CPU profile to this file.")
+       flag.StringVar(&memProfile, "mem-profile", memProfile, "Write memory profile to this file.")
+}
+
+// Load configuration file from disk, by default from /etc/opensnitchd/default-config.json,
+// or from the path specified by configFile.
+// This configuration will be loaded again by uiClient(), in order to monitor it for changes.
+func loadDiskConfiguration() (*config.Config, error) {
+       if configFile == "" {
+               return nil, fmt.Errorf("Configuration file cannot be empty")
+       }
+
+       raw, err := config.Load(configFile)
+       if err != nil || len(raw) == 0 {
+               return nil, fmt.Errorf("Error loading configuration %s: %s", configFile, err)
+       }
+       clientConfig, err := config.Parse(raw)
+       if err != nil {
+               return nil, fmt.Errorf("Error parsing configuration %s: %s", configFile, err)
+       }
+
+       log.Info("Loading configuration file %s ...", configFile)
+       return &clientConfig, nil
+}
+
+func overwriteLogging() bool {
+       return debug || warning || important || errorlog || logFile != "" || logMicro
+}
+
+func setupLogging() {
+       golog.SetOutput(ioutil.Discard)
+       if debug {
+               log.SetLogLevel(log.DEBUG)
+       } else if warning {
+               log.SetLogLevel(log.WARNING)
+       } else if important {
+               log.SetLogLevel(log.IMPORTANT)
+       } else if errorlog {
+               log.SetLogLevel(log.ERROR)
+       } else {
+               log.SetLogLevel(log.INFO)
+       }
+
+       log.SetLogUTC(logUTC)
+       log.SetLogMicro(logMicro)
+
+       var logFileToUse string
+       if logFile == "" {
+               logFileToUse = log.StdoutFile
+       } else {
+               logFileToUse = logFile
+       }
+       log.Close()
+       if err := log.OpenFile(logFileToUse); err != nil {
+               log.Error("Error opening user defined log: %s %s", logFileToUse, err)
+       }
+}
+
+func setupProfiling() {
+       if cpuProfile != "" {
+               if f, err := os.Create(cpuProfile); err != nil {
+                       log.Fatal("%s", err)
+               } else if err := pprof.StartCPUProfile(f); err != nil {
+                       log.Fatal("%s", err)
+               }
+       }
+}
+
+func setupSignals() {
+       sigChan = make(chan os.Signal, 1)
+       exitChan = make(chan bool, workers+1)
+       signal.Notify(sigChan,
+               syscall.SIGHUP,
+               syscall.SIGINT,
+               syscall.SIGTERM,
+               syscall.SIGQUIT)
+       go func() {
+               sig := <-sigChan
+               log.Raw("\n")
+               log.Important("Got signal: %v", sig)
+               cancel()
+               time.AfterFunc(10*time.Second, func() {
+                       log.Error("[REVIEW] closing due to timeout")
+                       os.Exit(0)
+               })
+       }()
+}
+
+func worker(id int) {
+       log.Debug("Worker #%d started.", id)
+       for true {
+               select {
+               case <-ctx.Done():
+                       goto Exit
+               default:
+                       pkt, ok := <-wrkChan
+                       if !ok {
+                               log.Debug("worker channel closed %d", id)
+                               goto Exit
+                       }
+                       onPacket(pkt)
+               }
+       }
+Exit:
+       log.Debug("worker #%d exit", id)
+}
+
+func setupWorkers() {
+       log.Debug("Starting %d workers ...", workers)
+       // setup the workers
+       wrkChan = make(chan netfilter.Packet)
+       for i := 0; i < workers; i++ {
+               go worker(i)
+       }
+}
+
+// Listen to events sent from other modules
+func listenToEvents() {
+       for i := 0; i < 5; i++ {
+               go func(uiClient *ui.Client) {
+                       for evt := range ebpf.Events() {
+                               // for loop vars are per-loop, not per-item
+                               evt := evt
+                               uiClient.PostAlert(
+                                       protocol.Alert_WARNING,
+                                       protocol.Alert_KERNEL_EVENT,
+                                       protocol.Alert_SHOW_ALERT,
+                                       protocol.Alert_MEDIUM,
+                                       evt)
+                       }
+               }(uiClient)
+       }
+}
+
+func initSystemdResolvedMonitor() {
+       resolvMonitor, err := systemd.NewResolvedMonitor()
+       if err != nil {
+               log.Debug("[DNS] Unable to use systemd-resolved monitor: %s", err)
+               return
+       }
+       _, err = resolvMonitor.Connect()
+       if err != nil {
+               log.Debug("[DNS] Connecting to systemd-resolved: %s", err)
+               return
+       }
+       err = resolvMonitor.Subscribe()
+       if err != nil {
+               log.Debug("[DNS] Subscribing to systemd-resolved DNS events: %s", err)
+               return
+       }
+       go func() {
+               for {
+                       select {
+                       case exit := <-resolvMonitor.Exit():
+                               if exit == nil {
+                                       log.Info("[DNS] systemd-resolved monitor stopped")
+                                       return
+                               }
+                               log.Debug("[DNS] systemd-resolved monitor disconnected. Reconnecting...")
+                       case response := <-resolvMonitor.GetDNSResponses():
+                               if response.State != systemd.SuccessState {
+                                       log.Debug("[DNS] systemd-resolved monitor response error: %v", response)
+                                       continue
+                               }
+                               /*for i, q := range response.Question {
+                                       log.Debug("%d SYSTEMD RESPONSE Q: %s", i, q.Name)
+                               }*/
+                               for i, a := range response.Answer {
+                                       if a.RR.Key.Type != systemd.DNSTypeA &&
+                                               a.RR.Key.Type != systemd.DNSTypeAAAA &&
+                                               a.RR.Key.Type != systemd.DNSTypeCNAME {
+                                               log.Debug("systemd-resolved, excluding answer: %#v", a)
+                                               continue
+                                       }
+                                       domain := a.RR.Key.Name
+                                       ip := net.IP(a.RR.Address)
+                                       log.Debug("%d systemd-resolved monitor response: %s -> %s", i, domain, ip)
+                                       if a.RR.Key.Type == systemd.DNSTypeCNAME {
+                                               log.Debug("systemd-resolved CNAME >> %s -> %s", a.RR.Name, domain)
+                                               dns.Track(a.RR.Name, domain)
+                                       } else {
+                                               dns.Track(ip.String(), domain)
+                                       }
+                               }
+                       }
+               }
+       }()
+}
+
+func doCleanup(queue, repeatQueue *netfilter.Queue) {
+       log.Info("Cleaning up ...")
+       firewall.Stop()
+       monitor.End()
+       uiClient.Close()
+       queue.Close()
+       repeatQueue.Close()
+       if resolvMonitor != nil {
+               resolvMonitor.Close()
+       }
+
+       if cpuProfile != "" {
+               pprof.StopCPUProfile()
+       }
+
+       if memProfile != "" {
+               f, err := os.Create(memProfile)
+               if err != nil {
+                       fmt.Printf("Could not create memory profile: %s\n", err)
+                       return
+               }
+               defer f.Close()
+               runtime.GC() // get up-to-date statistics
+               if err := pprof.WriteHeapProfile(f); err != nil {
+                       fmt.Printf("Could not write memory profile: %s\n", err)
+               }
+       }
+}
+
+func onPacket(packet netfilter.Packet) {
+       // DNS response, just parse, track and accept.
+       if dns.TrackAnswers(packet.Packet) == true {
+               packet.SetVerdictAndMark(netfilter.NF_ACCEPT, packet.Mark)
+               stats.OnDNSResponse()
+               return
+       }
+
+       // Parse the connection state
+       con := conman.Parse(packet, uiClient.InterceptUnknown())
+       if con == nil {
+               applyDefaultAction(&packet, nil)
+               return
+       }
+       // accept our own connections
+       if con.Process.ID == os.Getpid() {
+               packet.SetVerdict(netfilter.NF_ACCEPT)
+               return
+       }
+
+       // search a match in preloaded rules
+       r := acceptOrDeny(&packet, con)
+
+       if r != nil && r.Nolog {
+               return
+       }
+       // XXX: if a connection is not intercepted due to InterceptUnknown == false,
+       // it's not sent to the server, which leads to miss information.
+       stats.OnConnectionEvent(con, r, r == nil)
+}
+
+func applyDefaultAction(packet *netfilter.Packet, con *conman.Connection) {
+       if uiClient.DefaultAction() == rule.Allow {
+               packet.SetVerdictAndMark(netfilter.NF_ACCEPT, packet.Mark)
+               return
+       }
+       if uiClient.DefaultAction() == rule.Reject && con != nil {
+               netlink.KillSocket(con.Protocol, con.SrcIP, con.SrcPort, con.DstIP, con.DstPort)
+       }
+       packet.SetVerdict(netfilter.NF_DROP)
+}
+
+func acceptOrDeny(packet *netfilter.Packet, con *conman.Connection) *rule.Rule {
+       r := rules.FindFirstMatch(con)
+       if r == nil {
+               // no rule matched
+               // Note that as soon as we set a verdict on a packet, the next packet in the netfilter queue
+               // will begin to be processed even if this function hasn't yet returned
+
+               // send a request to the UI client if
+               // 1) connected and running and 2) we are not already asking
+               if uiClient.Connected() == false || uiClient.GetIsAsking() == true {
+                       applyDefaultAction(packet, con)
+                       log.Debug("UI is not running or busy, connected: %v, running: %v", uiClient.Connected(), uiClient.GetIsAsking())
+                       return nil
+               }
+
+               uiClient.SetIsAsking(true)
+               defer uiClient.SetIsAsking(false)
+
+               // In order not to block packet processing, we send our packet to a different netfilter queue
+               // and then immediately pull it back out of that queue
+               packet.SetRequeueVerdict(uint16(repeatQueueNum))
+
+               var o bool
+               var pkt netfilter.Packet
+               // don't wait for the packet longer than 1 sec
+               select {
+               case pkt, o = <-repeatPktChan:
+                       if !o {
+                               log.Debug("error while receiving packet from repeatPktChan")
+                               return nil
+                       }
+               case <-time.After(1 * time.Second):
+                       log.Debug("timed out while receiving packet from repeatPktChan")
+                       return nil
+               }
+
+               //check if the pulled out packet is the same we put in
+               if res := bytes.Compare(packet.Packet.Data(), pkt.Packet.Data()); res != 0 {
+                       log.Error("The packet which was requeued has changed abruptly. This should never happen. Please report this incident to the Opensnitch developers. %v %v ", packet, pkt)
+                       return nil
+               }
+               packet = &pkt
+
+               // Update the hostname again.
+               // This is required due to a race between the ebpf dns hook and the actual first packet beeing sent
+               if con.DstHost == "" {
+                       con.DstHost = dns.HostOr(con.DstIP, con.DstHost)
+               }
+
+               r = uiClient.Ask(con)
+               if r == nil {
+                       log.Error("Invalid rule received, applying default action")
+                       applyDefaultAction(packet, con)
+                       return nil
+               }
+               ok := false
+               pers := ""
+               action := string(r.Action)
+               if r.Action == rule.Allow {
+                       action = log.Green(action)
+               } else {
+                       action = log.Red(action)
+               }
+
+               // check if and how the rule needs to be saved
+               if r.Duration == rule.Always {
+                       pers = "Saved"
+                       // add to the loaded rules and persist on disk
+                       if err := rules.Add(r, true); err != nil {
+                               log.Error("Error while saving rule: %s", err)
+                       } else {
+                               ok = true
+                       }
+               } else {
+                       pers = "Added"
+                       // add to the rules but do not save to disk
+                       if err := rules.Add(r, false); err != nil {
+                               log.Error("Error while adding rule: %s", err)
+                       } else {
+                               ok = true
+                       }
+               }
+
+               if ok {
+                       log.Important("%s new rule: %s if %s", pers, action, r.Operator.String())
+               }
+
+       }
+       if packet == nil {
+               log.Debug("Packet nil after processing rules")
+               return r
+       }
+
+       if r.Enabled == false {
+               applyDefaultAction(packet, con)
+               ruleName := log.Green(r.Name)
+               log.Info("DISABLED (%s) %s %s -> %s:%d (%s)", uiClient.DefaultAction(), log.Bold(log.Green("✔")), log.Bold(con.Process.Path), log.Bold(con.To()), con.DstPort, ruleName)
+
+       } else if r.Action == rule.Allow {
+               packet.SetVerdictAndMark(netfilter.NF_ACCEPT, packet.Mark)
+               ruleName := log.Green(r.Name)
+               if r.Operator.Operand == rule.OpTrue {
+                       ruleName = log.Dim(r.Name)
+               }
+               log.Debug("%s %s -> %d:%s => %s:%d, mark: %x (%s)", log.Bold(log.Green("✔")), log.Bold(con.Process.Path), con.SrcPort, log.Bold(con.SrcIP.String()), log.Bold(con.To()), con.DstPort, packet.Mark, ruleName)
+       } else {
+               if r.Action == rule.Reject {
+                       netlink.KillSocket(con.Protocol, con.SrcIP, con.SrcPort, con.DstIP, con.DstPort)
+               }
+               packet.SetVerdict(netfilter.NF_DROP)
+
+               log.Debug("%s %s -> %d:%s => %s:%d, mark: %x (%s)", log.Bold(log.Red("✘")), log.Bold(con.Process.Path), con.SrcPort, log.Bold(con.SrcIP.String()), log.Bold(con.To()), con.DstPort, packet.Mark, log.Red(r.Name))
+       }
+
+       return r
+}
+
+func main() {
+       ctx, cancel = context.WithCancel(context.Background())
+       defer cancel()
+       flag.Parse()
+
+       if showVersion {
+               fmt.Println(core.Version)
+               os.Exit(0)
+       }
+       if checkRequirements {
+               core.CheckSysRequirements()
+               os.Exit(0)
+       }
+
+       setupLogging()
+       setupProfiling()
+
+       log.Important("Starting %s v%s", core.Name, core.Version)
+
+       cfg, err := loadDiskConfiguration()
+       if err != nil {
+               log.Fatal("%s", err)
+       }
+       if err == nil && cfg.Rules.Path != "" {
+               rulesPath = cfg.Rules.Path
+       }
+       if rulesPath == "" {
+               log.Fatal("rules path cannot be empty")
+       }
+
+       rulesPath, err := core.ExpandPath(rulesPath)
+       if err != nil {
+               log.Fatal("Error accessing rules path (does it exist?): %s", err)
+       }
+
+       setupSignals()
+
+       log.Info("Loading rules from %s ...", rulesPath)
+       rules, err = rule.NewLoader(!noLiveReload)
+       if err != nil {
+               log.Fatal("%s", err)
+       } else if err = rules.Load(rulesPath); err != nil {
+               log.Fatal("%s", err)
+       }
+       stats = statistics.New(rules)
+       loggerMgr = loggers.NewLoggerManager()
+       uiClient = ui.NewClient(uiSocket, configFile, stats, rules, loggerMgr)
+
+       // prepare the queue
+       setupWorkers()
+       queue, err := netfilter.NewQueue(uint16(queueNum))
+       if err != nil {
+               msg := fmt.Sprintf("Error creating queue #%d: %s", queueNum, err)
+               uiClient.SendWarningAlert(msg)
+               log.Warning("Is opensnitchd already running?")
+               log.Fatal(msg)
+       }
+       pktChan = queue.Packets()
+
+       repeatQueueNum = queueNum + 1
+       repeatQueue, rqerr := netfilter.NewQueue(uint16(repeatQueueNum))
+       if rqerr != nil {
+               msg := fmt.Sprintf("Error creating repeat queue #%d: %s", repeatQueueNum, rqerr)
+               uiClient.SendErrorAlert(msg)
+               log.Warning("Is opensnitchd already running?")
+               log.Warning(msg)
+       }
+       repeatPktChan = repeatQueue.Packets()
+
+       // queue is ready, run firewall rules and start intercepting connections
+       if err = firewall.Init(uiClient.GetFirewallType(), &queueNum); err != nil {
+               log.Warning("%s", err)
+               uiClient.SendWarningAlert(err)
+       }
+
+       uiClient.Connect()
+       listenToEvents()
+
+       if overwriteLogging() {
+               setupLogging()
+       }
+       // overwrite monitor method from configuration if the user has passed
+       // the option via command line.
+       if procmonMethod != "" {
+               if err := monitor.ReconfigureMonitorMethod(procmonMethod, cfg.Ebpf.ModulesPath); err != nil {
+                       msg := fmt.Sprintf("Unable to set process monitor method via parameter: %v", err)
+                       uiClient.SendWarningAlert(msg)
+                       log.Warning(msg)
+               }
+       }
+
+       go func(uiClient *ui.Client, ebpfPath string) {
+               if err := dns.ListenerEbpf(ebpfPath); err != nil {
+                       msg := fmt.Sprintf("EBPF-DNS: Unable to attach ebpf listener: %s", err)
+                       log.Warning(msg)
+                       // don't display an alert, since this module is not critical
+                       uiClient.PostAlert(
+                               protocol.Alert_ERROR,
+                               protocol.Alert_GENERIC,
+                               protocol.Alert_SAVE_TO_DB,
+                               protocol.Alert_MEDIUM,
+                               msg)
+
+               }
+       }(uiClient, cfg.Ebpf.ModulesPath)
+
+       initSystemdResolvedMonitor()
+
+       log.Info("Running on netfilter queue #%d ...", queueNum)
+       for {
+               select {
+               case <-ctx.Done():
+                       goto Exit
+               case pkt, ok := <-pktChan:
+                       if !ok {
+                               goto Exit
+                       }
+                       wrkChan <- pkt
+               }
+       }
+Exit:
+       close(wrkChan)
+       doCleanup(queue, repeatQueue)
+       os.Exit(0)
+}
diff --git a/daemon/netfilter/packet.go b/daemon/netfilter/packet.go
new file mode 100644 (file)
index 0000000..7c3e95b
--- /dev/null
@@ -0,0 +1,62 @@
+package netfilter
+
+import "C"
+
+import (
+       "github.com/google/gopacket"
+)
+
+// packet consts
+const (
+       IPv4 = 4
+)
+
+// Verdict holds the action to perform on a packet (NF_DROP, NF_ACCEPT, etc)
+type Verdict C.uint
+
+// VerdictContainer struct
+type VerdictContainer struct {
+       Mark    uint32
+       Verdict Verdict
+       Packet  []byte
+}
+
+// Packet holds the data of a network packet
+type Packet struct {
+       Packet          gopacket.Packet
+       verdictChannel  chan VerdictContainer
+       IfaceInIdx      int
+       IfaceOutIdx     int
+       Mark            uint32
+       UID             uint32
+       NetworkProtocol uint8
+}
+
+// SetVerdict emits a veredict on a packet
+func (p *Packet) SetVerdict(v Verdict) {
+       p.verdictChannel <- VerdictContainer{Verdict: v, Packet: nil, Mark: 0}
+}
+
+// SetVerdictAndMark emits a veredict on a packet and marks it in order to not
+// analyze it again.
+func (p *Packet) SetVerdictAndMark(v Verdict, mark uint32) {
+       p.verdictChannel <- VerdictContainer{Verdict: v, Packet: nil, Mark: mark}
+}
+
+// SetRequeueVerdict apply a verdict on a requeued packet
+func (p *Packet) SetRequeueVerdict(newQueueID uint16) {
+       v := uint(NF_QUEUE)
+       q := (uint(newQueueID) << 16)
+       v = v | q
+       p.verdictChannel <- VerdictContainer{Verdict: Verdict(v), Packet: nil, Mark: p.Mark}
+}
+
+// SetVerdictWithPacket apply a verdict, but with a new packet
+func (p *Packet) SetVerdictWithPacket(v Verdict, packet []byte) {
+       p.verdictChannel <- VerdictContainer{Verdict: v, Packet: packet, Mark: 0}
+}
+
+// IsIPv4 returns if the packet is IPv4
+func (p *Packet) IsIPv4() bool {
+       return p.NetworkProtocol == IPv4
+}
diff --git a/daemon/netfilter/queue.c b/daemon/netfilter/queue.c
new file mode 100644 (file)
index 0000000..f2b7ef6
--- /dev/null
@@ -0,0 +1,2 @@
+#include "queue.h"
+
diff --git a/daemon/netfilter/queue.go b/daemon/netfilter/queue.go
new file mode 100644 (file)
index 0000000..ac4cf7a
--- /dev/null
@@ -0,0 +1,246 @@
+package netfilter
+
+/*
+#cgo pkg-config: libnetfilter_queue
+#cgo CFLAGS: -I/usr/include
+#cgo LDFLAGS: -L/usr/lib64/ -ldl
+
+#include "queue.h"
+*/
+import "C"
+
+import (
+       "fmt"
+       "os"
+       "sync"
+       "syscall"
+       "time"
+       "unsafe"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/google/gopacket"
+       "github.com/google/gopacket/layers"
+       "golang.org/x/sys/unix"
+)
+
+const (
+       AF_INET  = 2
+       AF_INET6 = 10
+
+       NF_DROP   Verdict = 0
+       NF_ACCEPT Verdict = 1
+       NF_STOLEN Verdict = 2
+       NF_QUEUE  Verdict = 3
+       NF_REPEAT Verdict = 4
+       NF_STOP   Verdict = 5
+
+       NF_DEFAULT_QUEUE_SIZE  uint32 = 4096
+       NF_DEFAULT_PACKET_SIZE uint32 = 4096
+)
+
+var (
+       queueIndex     = make(map[uint32]*chan Packet, 0)
+       queueIndexLock = sync.RWMutex{}
+
+       gopacketDecodeOptions = gopacket.DecodeOptions{Lazy: true, NoCopy: true}
+)
+
+// VerdictContainerC is the struct that contains the mark, action, length and
+// payload of a packet.
+// It's defined in queue.h, and filled on go_callback()
+type VerdictContainerC C.verdictContainer
+
+// Queue holds the information of a netfilter queue.
+// The handles of the connection to the kernel and the created queue.
+// A channel where the intercepted packets will be received.
+// The ID of the queue.
+type Queue struct {
+       h       *C.struct_nfq_handle
+       qh      *C.struct_nfq_q_handle
+       packets chan Packet
+       fd      C.int
+       idx     uint32
+}
+
+// NewQueue opens a new netfilter queue to receive packets marked with a mark.
+func NewQueue(queueID uint16) (q *Queue, err error) {
+       q = &Queue{
+               idx:     uint32(time.Now().UnixNano()),
+               packets: make(chan Packet),
+       }
+
+       if err = q.create(queueID); err != nil {
+               return nil, err
+       } else if err = q.setup(); err != nil {
+               return nil, err
+       }
+
+       go q.run()
+
+       return q, nil
+}
+
+func (q *Queue) create(queueID uint16) (err error) {
+       var ret C.int
+
+       if q.h, err = C.nfq_open(); err != nil {
+               return fmt.Errorf("Error opening Queue handle: %v", err)
+       } else if ret, err = C.nfq_unbind_pf(q.h, AF_INET); err != nil || ret < 0 {
+               errmsg := fmt.Errorf("Error %d unbinding existing q handler from AF_INET protocol family: %v", ret, err)
+               if syscall.Errno(ret) == unix.EINVAL {
+                       errmsg = fmt.Errorf("%s\nRestarting your computer may help to solve this error (see issues: #323 and #912 for more information)", errmsg)
+               }
+               return errmsg
+       } else if ret, err = C.nfq_unbind_pf(q.h, AF_INET6); err != nil || ret < 0 {
+               return fmt.Errorf("Error (%d) unbinding existing q handler from AF_INET6 protocol family: %v", ret, err)
+       } else if ret, err := C.nfq_bind_pf(q.h, AF_INET); err != nil || ret < 0 {
+               return fmt.Errorf("Error (%d) binding to AF_INET protocol family: %v", ret, err)
+       } else if ret, err := C.nfq_bind_pf(q.h, AF_INET6); err != nil || ret < 0 {
+               return fmt.Errorf("Error (%d) binding to AF_INET6 protocol family: %v", ret, err)
+       } else if q.qh, err = C.CreateQueue(q.h, C.uint16_t(queueID), C.uint32_t(q.idx)); err != nil || q.qh == nil {
+               q.destroy()
+               return fmt.Errorf("Error binding to queue: %v", err)
+       }
+
+       queueIndexLock.Lock()
+       queueIndex[q.idx] = &q.packets
+       queueIndexLock.Unlock()
+
+       return nil
+}
+
+func (q *Queue) setup() (err error) {
+       var ret C.int
+
+       queueSize := C.uint32_t(NF_DEFAULT_QUEUE_SIZE)
+       bufferSize := C.uint(NF_DEFAULT_PACKET_SIZE)
+       totSize := C.uint(NF_DEFAULT_QUEUE_SIZE * NF_DEFAULT_PACKET_SIZE)
+
+       if ret, err = C.nfq_set_queue_maxlen(q.qh, queueSize); err != nil || ret < 0 {
+               q.destroy()
+               return fmt.Errorf("Unable to set max packets in queue: %v", err)
+       } else if C.nfq_set_mode(q.qh, C.uint8_t(2), bufferSize) < 0 {
+               q.destroy()
+               return fmt.Errorf("Unable to set packets copy mode: %v", err)
+       } else if q.fd, err = C.nfq_fd(q.h); err != nil {
+               q.destroy()
+               return fmt.Errorf("Unable to get queue file-descriptor. %v", err)
+       } else if C.nfnl_rcvbufsiz(C.nfq_nfnlh(q.h), totSize) < 0 {
+               q.destroy()
+               return fmt.Errorf("Unable to increase netfilter buffer space size")
+       }
+
+       return nil
+}
+
+func (q *Queue) run() {
+       if errno := C.Run(q.h, q.fd); errno != 0 {
+               fmt.Fprintf(os.Stderr, "Terminating, unable to receive packet due to errno=%d", errno)
+       }
+}
+
+// Close ensures that nfqueue resources are freed and closed.
+// C.stop_reading_packets() stops the reading packets loop, which causes
+// go-subroutine run() to exit.
+// After exit, listening queue is destroyed and closed.
+// If for some reason any of the steps stucks while closing it, we'll exit by timeout.
+func (q *Queue) Close() {
+       C.stop_reading_packets()
+       q.destroy()
+       queueIndexLock.Lock()
+       delete(queueIndex, q.idx)
+       queueIndexLock.Unlock()
+       close(q.packets)
+}
+
+func (q *Queue) destroy() {
+       // we'll try to exit cleanly, but sometimes nfqueue gets stuck
+       time.AfterFunc(5*time.Second, func() {
+               log.Warning("queue (%d) stuck, closing by timeout", q.idx)
+               if q != nil {
+                       C.close(q.fd)
+                       q.closeNfq()
+               }
+               os.Exit(0)
+       })
+       if q.qh != nil {
+               if ret := C.nfq_destroy_queue(q.qh); ret != 0 {
+                       log.Warning("Queue.destroy() idx=%d, nfq_destroy_queue() not closed: %d", q.idx, ret)
+               }
+       }
+
+       q.closeNfq()
+}
+
+func (q *Queue) closeNfq() {
+       if q.h != nil {
+               if ret := C.nfq_close(q.h); ret != 0 {
+                       log.Warning("Queue.destroy() idx=%d, nfq_close() not closed: %d", q.idx, ret)
+               }
+       }
+}
+
+// Packets return the list of enqueued packets.
+func (q *Queue) Packets() <-chan Packet {
+       return q.packets
+}
+
+// FYI: the export keyword is mandatory to specify that go_callback is defined elsewhere
+
+//export go_callback
+func go_callback(queueID C.int, data *C.uchar, length C.int, mark C.uint, idx uint32, vc *VerdictContainerC, uid, devIn, devOut uint32) {
+       (*vc).verdict = C.uint(NF_ACCEPT)
+       (*vc).data = nil
+       (*vc).mark_set = 0
+       (*vc).length = 0
+
+       queueIndexLock.RLock()
+       queueChannel, found := queueIndex[idx]
+       queueIndexLock.RUnlock()
+       if !found {
+               fmt.Fprintf(os.Stderr, "Unexpected queue idx %d\n", idx)
+               return
+       }
+
+       xdata := C.GoBytes(unsafe.Pointer(data), length)
+
+       p := Packet{
+               verdictChannel:  make(chan VerdictContainer),
+               Mark:            uint32(mark),
+               UID:             uid,
+               NetworkProtocol: xdata[0] >> 4, // first 4 bits is the version
+               IfaceInIdx:      int(devIn),
+               IfaceOutIdx:     int(devOut),
+       }
+
+       var packet gopacket.Packet
+       if p.IsIPv4() {
+               packet = gopacket.NewPacket(xdata, layers.LayerTypeIPv4, gopacketDecodeOptions)
+       } else {
+               packet = gopacket.NewPacket(xdata, layers.LayerTypeIPv6, gopacketDecodeOptions)
+       }
+
+       p.Packet = packet
+
+       select {
+       case *queueChannel <- p:
+               select {
+               case v := <-p.verdictChannel:
+                       if v.Packet == nil {
+                               (*vc).verdict = C.uint(v.Verdict)
+                       } else {
+                               (*vc).verdict = C.uint(v.Verdict)
+                               (*vc).data = (*C.uchar)(unsafe.Pointer(&v.Packet[0]))
+                               (*vc).length = C.uint(len(v.Packet))
+                       }
+
+                       if v.Mark != 0 {
+                               (*vc).mark_set = C.uint(1)
+                               (*vc).mark = C.uint(v.Mark)
+                       }
+               }
+
+       case <-time.After(1 * time.Millisecond):
+               fmt.Fprintf(os.Stderr, "Timed out while sending packet to queue channel %d\n", idx)
+       }
+}
diff --git a/daemon/netfilter/queue.h b/daemon/netfilter/queue.h
new file mode 100644 (file)
index 0000000..9c06aab
--- /dev/null
@@ -0,0 +1,117 @@
+#ifndef _NETFILTER_QUEUE_H
+#define _NETFILTER_QUEUE_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <math.h>
+#include <unistd.h>
+#include <dlfcn.h>
+#include <netinet/in.h>
+#include <linux/types.h>
+#include <linux/socket.h>
+#include <linux/netfilter.h>
+#include <libnetfilter_queue/libnetfilter_queue.h>
+
+typedef struct {
+    unsigned int verdict;
+    unsigned int mark;
+    unsigned int mark_set;
+    unsigned int length;
+    unsigned char *data;
+} verdictContainer;
+
+static void *get_uid = NULL;
+
+extern void go_callback(int id, unsigned char* data, int len, unsigned int mark, uint32_t idx, verdictContainer *vc, uint32_t uid, uint32_t in_dev, uint32_t out_dev);
+
+static uint8_t stop = 0;
+
+static inline void configure_uid_if_available(struct nfq_q_handle *qh){
+    void *hndl = dlopen("libnetfilter_queue.so.1", RTLD_LAZY);
+    if (!hndl) {
+        hndl = dlopen("libnetfilter_queue.so", RTLD_LAZY);
+        if (!hndl){
+            printf("WARNING: libnetfilter_queue not available\n");
+            return;
+        }
+    }
+    if ((get_uid = dlsym(hndl, "nfq_get_uid")) == NULL){
+        printf("WARNING: nfq_get_uid not available\n");
+        return;
+    }
+    printf("OK: libnetfiler_queue supports nfq_get_uid\n");
+#ifdef NFQA_CFG_F_UID_GID
+    if (qh != NULL && nfq_set_queue_flags(qh, NFQA_CFG_F_UID_GID, NFQA_CFG_F_UID_GID)){
+        printf("WARNING: UID not available on this kernel/libnetfilter_queue\n");
+    }
+#endif
+}
+
+static int nf_callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *arg){
+    if (stop) {
+        return -1;
+    }
+
+    uint32_t id = -1, idx = 0, mark = 0;
+    struct nfqnl_msg_packet_hdr *ph = NULL;
+    unsigned char *buffer = NULL;
+    int size = 0;
+    verdictContainer vc = {0};
+    uint32_t uid = 0xffffffff;
+    uint32_t in_dev=0, out_dev=0;
+
+    in_dev = nfq_get_indev(nfa);
+    out_dev = nfq_get_outdev(nfa);
+
+    mark = nfq_get_nfmark(nfa);
+    ph   = nfq_get_msg_packet_hdr(nfa);
+    id   = ntohl(ph->packet_id);
+    size = nfq_get_payload(nfa, &buffer);
+    idx  = (uint32_t)((uintptr_t)arg);
+
+#ifdef NFQA_CFG_F_UID_GID
+    if (get_uid)
+        nfq_get_uid(nfa, &uid);
+#endif
+
+    go_callback(id, buffer, size, mark, idx, &vc, uid, in_dev, out_dev);
+
+    if( vc.mark_set == 1 ) {
+      return nfq_set_verdict2(qh, id, vc.verdict, vc.mark, vc.length, vc.data);
+    }
+    return nfq_set_verdict2(qh, id, vc.verdict, vc.mark, vc.length, vc.data);
+}
+
+static inline struct nfq_q_handle* CreateQueue(struct nfq_handle *h, uint16_t queue, uint32_t idx) {
+    struct nfq_q_handle* qh = nfq_create_queue(h, queue, &nf_callback, (void*)((uintptr_t)idx));
+    if (qh == NULL){
+        printf("ERROR: nfq_create_queue() queue not created\n");
+    } else {
+        configure_uid_if_available(qh);
+    }
+    return qh;
+}
+
+static inline void stop_reading_packets() {
+    stop = 1;
+}
+
+static inline int Run(struct nfq_handle *h, int fd) {
+    char buf[4096] __attribute__ ((aligned));
+    int rcvd, opt = 1;
+
+    setsockopt(fd, SOL_NETLINK, NETLINK_NO_ENOBUFS, &opt, sizeof(int));
+
+    while ((rcvd = recv(fd, buf, sizeof(buf), 0)) >= 0) {
+        if (stop == 1) {
+            return errno;
+        }
+        nfq_handle_packet(h, buf, rcvd);
+    }
+
+    return errno;
+}
+
+#endif
diff --git a/daemon/netlink/ifaces.go b/daemon/netlink/ifaces.go
new file mode 100644 (file)
index 0000000..d96d8ed
--- /dev/null
@@ -0,0 +1,47 @@
+package netlink
+
+import (
+       "net"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/vishvananda/netlink"
+)
+
+// https://cs.opensource.google/go/go/+/refs/tags/go1.20.6:src/net/ip.go;l=133
+// TODO: remove when upgrading go version.
+func isPrivate(ip net.IP) bool {
+       if ip4 := ip.To4(); ip4 != nil {
+               return ip4[0] == 10 ||
+                       (ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
+                       (ip4[0] == 192 && ip4[1] == 168)
+       }
+       return len(ip) == 16 && ip[0]&0xfe == 0xfc
+}
+
+// GetLocalAddrs returns the list of local IPs
+func GetLocalAddrs() map[string]netlink.Addr {
+       localAddresses := make(map[string]netlink.Addr)
+       addr, err := netlink.AddrList(nil, netlink.FAMILY_ALL)
+       if err != nil {
+               log.Error("eBPF error looking up this machine's addresses via netlink: %v", err)
+               return nil
+       }
+       for _, a := range addr {
+               log.Debug("local addr: %+v\n", a)
+               localAddresses[a.IP.String()] = a
+       }
+
+       return localAddresses
+}
+
+// AddrUpdateToAddr translates AddrUpdate struct to Addr.
+func AddrUpdateToAddr(addr *netlink.AddrUpdate) netlink.Addr {
+       return netlink.Addr{
+               IPNet:       &addr.LinkAddress,
+               LinkIndex:   addr.LinkIndex,
+               Flags:       addr.Flags,
+               Scope:       addr.Scope,
+               PreferedLft: addr.PreferedLft,
+               ValidLft:    addr.ValidLft,
+       }
+}
diff --git a/daemon/netlink/socket.go b/daemon/netlink/socket.go
new file mode 100644 (file)
index 0000000..73a04aa
--- /dev/null
@@ -0,0 +1,230 @@
+package netlink
+
+import (
+       "fmt"
+       "net"
+       "strconv"
+       "syscall"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/vishvananda/netlink"
+       "golang.org/x/sys/unix"
+)
+
+// GetSocketInfo asks the kernel via netlink for a given connection.
+// If the connection is found, we return the uid and the possible
+// associated inodes.
+// If the outgoing connection is not found but there're entries with the source
+// port and same protocol, add all the inodes to the list.
+//
+// Some examples:
+// outgoing connection as seen by netfilter || connection details dumped from kernel
+//
+// 47344:192.168.1.106 -> 151.101.65.140:443 || in kernel: 47344:192.168.1.106 -> 151.101.65.140:443
+// 8612:192.168.1.5 -> 192.168.1.255:8612  || in kernel: 8612:192.168.1.105 -> 0.0.0.0:0
+// 123:192.168.1.5  -> 217.144.138.234:123 || in kernel: 123:0.0.0.0 -> 0.0.0.0:0
+// 45015:127.0.0.1 -> 239.255.255.250:1900 || in kernel: 45015:127.0.0.1 -> 0.0.0.0:0
+// 50416:fe80::9fc2:ddcf:df22:aa50 -> fe80::1:53 || in kernel: 50416:254.128.0.0 -> 254.128.0.0:53
+// 51413:192.168.1.106 -> 103.224.182.250:1337 || in kernel: 51413:0.0.0.0 -> 0.0.0.0:0
+func GetSocketInfo(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) (uid int, inodes []int) {
+       uid = -1
+       family := uint8(syscall.AF_INET)
+       ipproto := uint8(syscall.IPPROTO_TCP)
+       protoLen := len(proto)
+       if proto[protoLen-1:protoLen] == "6" {
+               family = syscall.AF_INET6
+       }
+
+       if proto[:3] == "udp" {
+               ipproto = syscall.IPPROTO_UDP
+               if protoLen >= 7 && proto[:7] == "udplite" {
+                       ipproto = syscall.IPPROTO_UDPLITE
+               }
+       }
+       if protoLen >= 4 && proto[:4] == "sctp" {
+               ipproto = syscall.IPPROTO_SCTP
+       }
+       if protoLen >= 4 && proto[:4] == "icmp" {
+               ipproto = syscall.IPPROTO_RAW
+       }
+       if sockList, err := SocketGet(family, ipproto, uint16(srcPort), uint16(dstPort), srcIP, dstIP); err == nil {
+               for n, sock := range sockList {
+                       if sock.UID != 0xffffffff {
+                               uid = int(sock.UID)
+                       }
+                       log.Debug("[%d/%d] outgoing connection uid: %d, %d:%v -> %v:%d || netlink response: %d:%v -> %v:%d inode: %d - loopback: %v multicast: %v unspecified: %v linklocalunicast: %v ifaceLocalMulticast: %v GlobalUni: %v ",
+                               n, len(sockList),
+                               int(sock.UID),
+                               srcPort, srcIP, dstIP, dstPort,
+                               sock.ID.SourcePort, sock.ID.Source,
+                               sock.ID.Destination, sock.ID.DestinationPort, sock.INode,
+                               sock.ID.Destination.IsLoopback(),
+                               sock.ID.Destination.IsMulticast(),
+                               sock.ID.Destination.IsUnspecified(),
+                               sock.ID.Destination.IsLinkLocalUnicast(),
+                               sock.ID.Destination.IsLinkLocalMulticast(),
+                               sock.ID.Destination.IsGlobalUnicast(),
+                       )
+
+                       if sock.ID.SourcePort == uint16(srcPort) && sock.ID.Source.Equal(srcIP) &&
+                               (sock.ID.DestinationPort == uint16(dstPort)) &&
+                               ((sock.ID.Destination.IsGlobalUnicast() || sock.ID.Destination.IsLoopback()) && sock.ID.Destination.Equal(dstIP)) {
+                               inodes = append([]int{int(sock.INode)}, inodes...)
+                               continue
+                       }
+                       log.Debug("GetSocketInfo() invalid: %d:%v -> %v:%d", sock.ID.SourcePort, sock.ID.Source, sock.ID.Destination, sock.ID.DestinationPort)
+               }
+
+               // handle special cases (see function description): ntp queries (123), broadcasts, incomming connections.
+               if len(inodes) == 0 && len(sockList) > 0 {
+                       for n, sock := range sockList {
+                               if sockList[n].ID.Destination.Equal(net.IPv4zero) || sockList[n].ID.Destination.Equal(net.IPv6zero) {
+                                       inodes = append([]int{int(sock.INode)}, inodes...)
+                                       log.Debug("netlink socket not found, adding entry:  %d:%v -> %v:%d || %d:%v -> %v:%d inode: %d state: %s",
+                                               srcPort, srcIP, dstIP, dstPort,
+                                               sockList[n].ID.SourcePort, sockList[n].ID.Source,
+                                               sockList[n].ID.Destination, sockList[n].ID.DestinationPort,
+                                               sockList[n].INode, TCPStatesMap[sock.State])
+                               } else if sock.ID.SourcePort == uint16(srcPort) && sock.ID.Source.Equal(srcIP) &&
+                                       (sock.ID.DestinationPort == uint16(dstPort)) {
+                                       inodes = append([]int{int(sock.INode)}, inodes...)
+                                       continue
+                               } else {
+                                       log.Debug("netlink socket not found, EXCLUDING entry:  %d:%v -> %v:%d || %d:%v -> %v:%d inode: %d state: %s",
+                                               srcPort, srcIP, dstIP, dstPort,
+                                               sockList[n].ID.SourcePort, sockList[n].ID.Source,
+                                               sockList[n].ID.Destination, sockList[n].ID.DestinationPort,
+                                               sockList[n].INode, TCPStatesMap[sock.State])
+                               }
+                       }
+               }
+       } else {
+               log.Debug("netlink socket error: %v - %d:%v -> %v:%d", err, srcPort, srcIP, dstIP, dstPort)
+       }
+
+       return uid, inodes
+}
+
+// GetSocketInfoByInode dumps the kernel sockets table and searches the given
+// inode on it.
+func GetSocketInfoByInode(inodeStr string) (*Socket, error) {
+       inode, err := strconv.ParseUint(inodeStr, 10, 32)
+       if err != nil {
+               return nil, err
+       }
+
+       type inetStruct struct{ family, proto uint8 }
+       socketTypes := []inetStruct{
+               {syscall.AF_INET, syscall.IPPROTO_TCP},
+               {syscall.AF_INET, syscall.IPPROTO_UDP},
+               {syscall.AF_INET6, syscall.IPPROTO_TCP},
+               {syscall.AF_INET6, syscall.IPPROTO_UDP},
+       }
+
+       for _, socket := range socketTypes {
+               socketList, err := SocketsDump(socket.family, socket.proto)
+               if err != nil {
+                       return nil, err
+               }
+               for idx := range socketList {
+                       if uint32(inode) == socketList[idx].INode {
+                               return socketList[idx], nil
+                       }
+               }
+       }
+       return nil, fmt.Errorf("Inode not found")
+}
+
+// KillSocket kills a socket given the properties of a connection.
+func KillSocket(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) {
+       family := uint8(syscall.AF_INET)
+       ipproto := uint8(syscall.IPPROTO_TCP)
+       protoLen := len(proto)
+       if proto[protoLen-1:protoLen] == "6" {
+               family = syscall.AF_INET6
+       }
+
+       if proto[:3] == "udp" {
+               ipproto = syscall.IPPROTO_UDP
+               if protoLen >= 7 && proto[:7] == "udplite" {
+                       ipproto = syscall.IPPROTO_UDPLITE
+               }
+       }
+
+       if sockList, err := SocketGet(family, ipproto, uint16(srcPort), uint16(dstPort), srcIP, dstIP); err == nil {
+               for _, s := range sockList {
+                       if err := SocketKill(family, ipproto, s.ID); err != nil {
+                               log.Debug("Unable to kill socket: %d, %d, %v", srcPort, dstPort, err)
+                       }
+               }
+       }
+}
+
+// KillSockets kills all sockets given a family and a protocol.
+// Be careful if you don't exclude local sockets, many local servers may misbehave,
+// entering in an infinite loop.
+func KillSockets(fam, proto uint8, excludeLocal bool) error {
+       sockListTCP, err := SocketsDump(fam, proto)
+       if err != nil {
+               return fmt.Errorf("eBPF could not dump TCP (%d/%d) sockets via netlink: %v", fam, proto, err)
+       }
+
+       for _, sock := range sockListTCP {
+               if excludeLocal && (isPrivate(sock.ID.Destination) ||
+                       sock.ID.Source.IsUnspecified() ||
+                       sock.ID.Destination.IsUnspecified()) {
+                       continue
+               }
+               if err := SocketKill(fam, proto, sock.ID); err != nil {
+                       log.Debug("Unable to kill socket (%+v): %s", sock.ID, err)
+               }
+       }
+
+       return nil
+}
+
+// KillAllSockets kills the sockets for the given families and protocols.
+func KillAllSockets() {
+       type opts struct {
+               fam   uint8
+               proto uint8
+       }
+       optList := []opts{
+               // add families and protos as wish
+               {unix.AF_INET, uint8(syscall.IPPROTO_TCP)},
+               {unix.AF_INET6, uint8(syscall.IPPROTO_TCP)},
+               {unix.AF_INET, uint8(syscall.IPPROTO_UDP)},
+               {unix.AF_INET6, uint8(syscall.IPPROTO_UDP)},
+               {unix.AF_INET, uint8(syscall.IPPROTO_SCTP)},
+               {unix.AF_INET6, uint8(syscall.IPPROTO_SCTP)},
+       }
+       for _, opt := range optList {
+               KillSockets(opt.fam, opt.proto, true)
+       }
+
+}
+
+// FlushConnections flushes conntrack as soon as netfilter rule is set.
+// This ensures that already-established connections will go to netfilter queue.
+func FlushConnections() {
+       if err := netlink.ConntrackTableFlush(netlink.ConntrackTable); err != nil {
+               log.Error("error flushing ConntrackTable %s", err)
+       }
+       if err := netlink.ConntrackTableFlush(netlink.ConntrackExpectTable); err != nil {
+               log.Error("error flusing ConntrackExpectTable %s", err)
+       }
+
+       // Force established connections to reestablish again.
+       KillAllSockets()
+}
+
+// SocketsAreEqual compares 2 different sockets to see if they match.
+func SocketsAreEqual(aSocket, bSocket *Socket) bool {
+       return ((*aSocket).INode == (*bSocket).INode &&
+               //inodes are unique enough, so the matches below will never have to be checked
+               (*aSocket).ID.SourcePort == (*bSocket).ID.SourcePort &&
+               (*aSocket).ID.Source.Equal((*bSocket).ID.Source) &&
+               (*aSocket).ID.Destination.Equal((*bSocket).ID.Destination) &&
+               (*aSocket).ID.DestinationPort == (*bSocket).ID.DestinationPort &&
+               (*aSocket).UID == (*bSocket).UID)
+}
diff --git a/daemon/netlink/socket_linux.go b/daemon/netlink/socket_linux.go
new file mode 100644 (file)
index 0000000..caf1d53
--- /dev/null
@@ -0,0 +1,264 @@
+package netlink
+
+import (
+       "encoding/binary"
+       "errors"
+       "fmt"
+       "net"
+       "syscall"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/vishvananda/netlink/nl"
+)
+
+// This is a modification of https://github.com/vishvananda/netlink socket_linux.go - Apache2.0 license
+// which adds support for query UDP, UDPLITE and IPv6 sockets to SocketGet()
+
+const (
+       SOCK_DESTROY        = 21
+       sizeofSocketID      = 0x30
+       sizeofSocketRequest = sizeofSocketID + 0x8
+       sizeofSocket        = sizeofSocketID + 0x18
+)
+
+var (
+       native       = nl.NativeEndian()
+       networkOrder = binary.BigEndian
+       TCP_ALL      = uint32(0xfff)
+)
+
+// https://elixir.bootlin.com/linux/latest/source/include/net/tcp_states.h
+const (
+       TCP_INVALID = iota
+       TCP_ESTABLISHED
+       TCP_SYN_SENT
+       TCP_SYN_RECV
+       TCP_FIN_WAIT1
+       TCP_FIN_WAIT2
+       TCP_TIME_WAIT
+       TCP_CLOSE
+       TCP_CLOSE_WAIT
+       TCP_LAST_ACK
+       TCP_LISTEN
+       TCP_CLOSING
+       TCP_NEW_SYN_REC
+       TCP_MAX_STATES
+)
+
+// TCPStatesMap holds the list of TCP states
+var TCPStatesMap = map[uint8]string{
+       TCP_INVALID:     "invalid",
+       TCP_ESTABLISHED: "established",
+       TCP_SYN_SENT:    "syn_sent",
+       TCP_SYN_RECV:    "syn_recv",
+       TCP_FIN_WAIT1:   "fin_wait1",
+       TCP_FIN_WAIT2:   "fin_wait2",
+       TCP_TIME_WAIT:   "time_wait",
+       TCP_CLOSE:       "close",
+       TCP_CLOSE_WAIT:  "close_wait",
+       TCP_LAST_ACK:    "last_ack",
+       TCP_LISTEN:      "listen",
+       TCP_CLOSING:     "closing",
+}
+
+// SocketID holds the socket information of a request/response to the kernel
+type SocketID struct {
+       SourcePort      uint16
+       DestinationPort uint16
+       Source          net.IP
+       Destination     net.IP
+       Interface       uint32
+       Cookie          [2]uint32
+}
+
+// Socket represents a netlink socket.
+type Socket struct {
+       Family  uint8
+       State   uint8
+       Timer   uint8
+       Retrans uint8
+       ID      SocketID
+       Expires uint32
+       RQueue  uint32
+       WQueue  uint32
+       UID     uint32
+       INode   uint32
+}
+
+// SocketRequest holds the request/response of a connection to the kernel
+type SocketRequest struct {
+       Family   uint8
+       Protocol uint8
+       Ext      uint8
+       pad      uint8
+       States   uint32
+       ID       SocketID
+}
+
+type writeBuffer struct {
+       Bytes []byte
+       pos   int
+}
+
+func (b *writeBuffer) Write(c byte) {
+       b.Bytes[b.pos] = c
+       b.pos++
+}
+
+func (b *writeBuffer) Next(n int) []byte {
+       s := b.Bytes[b.pos : b.pos+n]
+       b.pos += n
+       return s
+}
+
+// Serialize convert SocketRequest struct to bytes.
+func (r *SocketRequest) Serialize() []byte {
+       b := writeBuffer{Bytes: make([]byte, sizeofSocketRequest)}
+       b.Write(r.Family)
+       b.Write(r.Protocol)
+       b.Write(r.Ext)
+       b.Write(r.pad)
+       native.PutUint32(b.Next(4), r.States)
+       networkOrder.PutUint16(b.Next(2), r.ID.SourcePort)
+       networkOrder.PutUint16(b.Next(2), r.ID.DestinationPort)
+       if r.Family == syscall.AF_INET6 {
+               copy(b.Next(16), r.ID.Source)
+               copy(b.Next(16), r.ID.Destination)
+       } else {
+               copy(b.Next(4), r.ID.Source.To4())
+               b.Next(12)
+               copy(b.Next(4), r.ID.Destination.To4())
+               b.Next(12)
+       }
+       native.PutUint32(b.Next(4), r.ID.Interface)
+       native.PutUint32(b.Next(4), r.ID.Cookie[0])
+       native.PutUint32(b.Next(4), r.ID.Cookie[1])
+       return b.Bytes
+}
+
+// Len returns the size of a socket request
+func (r *SocketRequest) Len() int { return sizeofSocketRequest }
+
+type readBuffer struct {
+       Bytes []byte
+       pos   int
+}
+
+func (b *readBuffer) Read() byte {
+       c := b.Bytes[b.pos]
+       b.pos++
+       return c
+}
+
+func (b *readBuffer) Next(n int) []byte {
+       s := b.Bytes[b.pos : b.pos+n]
+       b.pos += n
+       return s
+}
+
+func (s *Socket) deserialize(b []byte) error {
+       if len(b) < sizeofSocket {
+               return fmt.Errorf("socket data short read (%d); want %d", len(b), sizeofSocket)
+       }
+       rb := readBuffer{Bytes: b}
+       s.Family = rb.Read()
+       s.State = rb.Read()
+       s.Timer = rb.Read()
+       s.Retrans = rb.Read()
+       s.ID.SourcePort = networkOrder.Uint16(rb.Next(2))
+       s.ID.DestinationPort = networkOrder.Uint16(rb.Next(2))
+       if s.Family == syscall.AF_INET6 {
+               s.ID.Source = net.IP(rb.Next(16))
+               s.ID.Destination = net.IP(rb.Next(16))
+       } else {
+               s.ID.Source = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read())
+               rb.Next(12)
+               s.ID.Destination = net.IPv4(rb.Read(), rb.Read(), rb.Read(), rb.Read())
+               rb.Next(12)
+       }
+       s.ID.Interface = native.Uint32(rb.Next(4))
+       s.ID.Cookie[0] = native.Uint32(rb.Next(4))
+       s.ID.Cookie[1] = native.Uint32(rb.Next(4))
+       s.Expires = native.Uint32(rb.Next(4))
+       s.RQueue = native.Uint32(rb.Next(4))
+       s.WQueue = native.Uint32(rb.Next(4))
+       s.UID = native.Uint32(rb.Next(4))
+       s.INode = native.Uint32(rb.Next(4))
+       return nil
+}
+
+// SocketKill kills a connection
+func SocketKill(family, proto uint8, sockID SocketID) error {
+
+       sockReq := &SocketRequest{
+               Family:   family,
+               Protocol: proto,
+               ID:       sockID,
+       }
+
+       req := nl.NewNetlinkRequest(SOCK_DESTROY, syscall.NLM_F_REQUEST|syscall.NLM_F_ACK)
+       req.AddData(sockReq)
+       _, err := req.Execute(syscall.NETLINK_INET_DIAG, 0)
+       if err != nil {
+               return err
+       }
+       return nil
+}
+
+// SocketGet returns the list of active connections in the kernel
+// filtered by several fields. Currently it returns connections
+// filtered by source port and protocol.
+func SocketGet(family uint8, proto uint8, srcPort, dstPort uint16, local, remote net.IP) ([]*Socket, error) {
+       _Id := SocketID{
+               SourcePort: srcPort,
+               Cookie:     [2]uint32{nl.TCPDIAG_NOCOOKIE, nl.TCPDIAG_NOCOOKIE},
+       }
+
+       sockReq := &SocketRequest{
+               Family:   family,
+               Protocol: proto,
+               States:   TCP_ALL,
+               ID:       _Id,
+       }
+
+       return netlinkRequest(sockReq, family, proto, srcPort, dstPort, local, remote)
+}
+
+// SocketsDump returns the list of all connections from the kernel
+func SocketsDump(family uint8, proto uint8) ([]*Socket, error) {
+       sockReq := &SocketRequest{
+               Family:   family,
+               Protocol: proto,
+               States:   TCP_ALL,
+       }
+       return netlinkRequest(sockReq, 0, 0, 0, 0, nil, nil)
+}
+
+func netlinkRequest(sockReq *SocketRequest, family uint8, proto uint8, srcPort, dstPort uint16, local, remote net.IP) ([]*Socket, error) {
+       req := nl.NewNetlinkRequest(nl.SOCK_DIAG_BY_FAMILY, syscall.NLM_F_DUMP)
+       req.AddData(sockReq)
+       msgs, err := req.Execute(syscall.NETLINK_INET_DIAG, 0)
+       if err != nil {
+               return nil, err
+       }
+       if len(msgs) == 0 {
+               return nil, errors.New("Warning, no message nor error from netlink, or no connections found")
+       }
+       var sock []*Socket
+       for n, m := range msgs {
+               s := &Socket{}
+               if err = s.deserialize(m); err != nil {
+                       log.Error("[%d] netlink socket error: %s, %d:%v -> %v:%d -  %d:%v -> %v:%d",
+                               n, TCPStatesMap[s.State],
+                               srcPort, local, remote, dstPort,
+                               s.ID.SourcePort, s.ID.Source, s.ID.Destination, s.ID.DestinationPort)
+                       continue
+               }
+               if s.INode == 0 {
+                       continue
+               }
+
+               sock = append([]*Socket{s}, sock...)
+       }
+       return sock, err
+}
diff --git a/daemon/netlink/socket_test.go b/daemon/netlink/socket_test.go
new file mode 100644 (file)
index 0000000..b37719b
--- /dev/null
@@ -0,0 +1,116 @@
+package netlink
+
+import (
+       "fmt"
+       "net"
+       "os"
+       "strconv"
+       "strings"
+       "testing"
+)
+
+type Connection struct {
+       SrcIP    net.IP
+       DstIP    net.IP
+       Protocol string
+       SrcPort  uint
+       DstPort  uint
+       OutConn  net.Conn
+       Listener net.Listener
+}
+
+func EstablishConnection(proto, dst string) (net.Conn, error) {
+       c, err := net.Dial(proto, dst)
+       if err != nil {
+               fmt.Println(err)
+               return nil, err
+       }
+       return c, nil
+}
+
+func ListenOnPort(proto, port string) (net.Listener, error) {
+       // TODO: UDP -> ListenUDP() or ListenPacket()
+       l, err := net.Listen(proto, port)
+       if err != nil {
+               fmt.Println(err)
+               return nil, err
+       }
+       return l, nil
+}
+
+func setupConnection(proto string, connChan chan *Connection) {
+       listnr, _ := ListenOnPort(proto, "127.0.0.1:55555")
+       conn, err := EstablishConnection(proto, "127.0.0.1:55555")
+       if err != nil {
+               connChan <- nil
+               return
+       }
+       laddr := strings.Split(conn.LocalAddr().String(), ":")
+       daddr := strings.Split(conn.RemoteAddr().String(), ":")
+       sport, _ := strconv.Atoi(laddr[1])
+       dport, _ := strconv.Atoi(daddr[1])
+
+       lconn := &Connection{
+               SrcPort:  uint(sport),
+               DstPort:  uint(dport),
+               SrcIP:    net.ParseIP(laddr[0]),
+               DstIP:    net.ParseIP(daddr[0]),
+               Protocol: "tcp",
+               Listener: listnr,
+               OutConn:  conn,
+       }
+       connChan <- lconn
+}
+
+// TestNetlinkQueries tests queries to the kernel to get the inode of a connection.
+// When using ProcFS as monitor method, we need that value to get the PID of an application.
+// We also need it if for any reason auditd or ebpf doesn't return the PID of the application.
+// TODO: test all the cases described in the GetSocketInfo() description.
+func TestNetlinkTCPQueries(t *testing.T) {
+       // netlink tests disabled by default, they cause random failures on restricted
+       // environments.
+       if os.Getenv("NETLINK_TESTS") == "" {
+               t.Skip("Skipping netlink tests. Use NETLINK_TESTS=1 to launch these tests.")
+       }
+
+       connChan := make(chan *Connection)
+       go setupConnection("tcp", connChan)
+       conn := <-connChan
+       if conn == nil {
+               t.Error("TestParseTCPConnection, conn nil")
+       }
+
+       var inodes []int
+       uid := -1
+       t.Run("Test GetSocketInfo", func(t *testing.T) {
+               uid, inodes = GetSocketInfo("tcp", conn.SrcIP, conn.SrcPort, conn.DstIP, conn.DstPort)
+
+               if len(inodes) == 0 {
+                       t.Error("inodes empty")
+               }
+               if uid != os.Getuid() {
+                       t.Error("GetSocketInfo UID error:", uid, os.Getuid())
+               }
+       })
+
+       t.Run("Test GetSocketInfoByInode", func(t *testing.T) {
+               socket, err := GetSocketInfoByInode(fmt.Sprint(inodes[0]))
+               if err != nil {
+                       t.Error("GetSocketInfoByInode error:", err)
+               }
+               if socket == nil {
+                       t.Error("GetSocketInfoByInode inode not found")
+               }
+               if socket.ID.SourcePort != uint16(conn.SrcPort) {
+                       t.Error("GetSocketInfoByInode dstPort error:", socket)
+               }
+               if socket.ID.DestinationPort != uint16(conn.DstPort) {
+                       t.Error("GetSocketInfoByInode dstPort error:", socket)
+               }
+               if socket.UID != uint32(os.Getuid()) {
+                       t.Error("GetSocketInfoByInode UID error:", socket, os.Getuid())
+               }
+       })
+
+       conn.Listener.Close()
+}
diff --git a/daemon/netstat/entry.go b/daemon/netstat/entry.go
new file mode 100644 (file)
index 0000000..ebe433a
--- /dev/null
@@ -0,0 +1,32 @@
+package netstat
+
+import (
+       "net"
+)
+
+// Entry holds the information of a /proc/net/* entry.
+// For example, /proc/net/tcp:
+// sl  local_address rem_address   st tx_queue rx_queue tr tm->when retrnsmt   uid  timeout inode
+// 0:  0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000  1000        0 18083222
+type Entry struct {
+       Proto   string
+       SrcIP   net.IP
+       DstIP   net.IP
+       UserId  int
+       INode   int
+       SrcPort uint
+       DstPort uint
+}
+
+// NewEntry creates a new entry with values from /proc/net/
+func NewEntry(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint, userId int, iNode int) Entry {
+       return Entry{
+               Proto:   proto,
+               SrcIP:   srcIP,
+               SrcPort: srcPort,
+               DstIP:   dstIP,
+               DstPort: dstPort,
+               UserId:  userId,
+               INode:   iNode,
+       }
+}
diff --git a/daemon/netstat/find.go b/daemon/netstat/find.go
new file mode 100644 (file)
index 0000000..8560c65
--- /dev/null
@@ -0,0 +1,51 @@
+package netstat
+
+import (
+       "net"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+// FindEntry looks for the connection in the list of known connections in ProcFS.
+func FindEntry(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) *Entry {
+       if entry := findEntryForProtocol(proto, srcIP, srcPort, dstIP, dstPort); entry != nil {
+               return entry
+       }
+
+       ipv6Suffix := "6"
+       if core.IPv6Enabled && strings.HasSuffix(proto, ipv6Suffix) == false {
+               otherProto := proto + ipv6Suffix
+               log.Debug("Searching for %s netstat entry instead of %s", otherProto, proto)
+               if entry := findEntryForProtocol(otherProto, srcIP, srcPort, dstIP, dstPort); entry != nil {
+                       return entry
+               }
+       }
+
+       return &Entry{
+               Proto:   proto,
+               SrcIP:   srcIP,
+               SrcPort: srcPort,
+               DstIP:   dstIP,
+               DstPort: dstPort,
+               UserId:  -1,
+               INode:   -1,
+       }
+}
+
+func findEntryForProtocol(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) *Entry {
+       entries, err := Parse(proto)
+       if err != nil {
+               log.Warning("Error while searching for %s netstat entry: %s", proto, err)
+               return nil
+       }
+
+       for _, entry := range entries {
+               if srcIP.Equal(entry.SrcIP) && srcPort == entry.SrcPort && dstIP.Equal(entry.DstIP) && dstPort == entry.DstPort {
+                       return &entry
+               }
+       }
+
+       return nil
+}
diff --git a/daemon/netstat/parse.go b/daemon/netstat/parse.go
new file mode 100644 (file)
index 0000000..3484119
--- /dev/null
@@ -0,0 +1,119 @@
+package netstat
+
+import (
+       "bufio"
+       "encoding/binary"
+       "net"
+       "os"
+       "regexp"
+       "strconv"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+var (
+       parser = regexp.MustCompile(`(?i)` +
+               `\d+:\s+` + // sl
+               `([a-f0-9]{8,32}):([a-f0-9]{4})\s+` + // local_address
+               `([a-f0-9]{8,32}):([a-f0-9]{4})\s+` + // rem_address
+               `[a-f0-9]{2}\s+` + // st
+               `[a-f0-9]{8}:[a-f0-9]{8}\s+` + // tx_queue rx_queue
+               `[a-f0-9]{2}:[a-f0-9]{8}\s+` + // tr tm->when
+               `[a-f0-9]{8}\s+` + // retrnsmt
+               `(\d+)\s+` + // uid
+               `\d+\s+` + // timeout
+               `(\d+)\s+` + // inode
+               `.+`) // stuff we don't care about
+)
+
+func decToInt(n string) int {
+       d, err := strconv.ParseInt(n, 10, 64)
+       if err != nil {
+               log.Fatal("Error while parsing %s to int: %s", n, err)
+       }
+       return int(d)
+}
+
+func hexToInt(h string) uint {
+       d, err := strconv.ParseUint(h, 16, 64)
+       if err != nil {
+               log.Fatal("Error while parsing %s to int: %s", h, err)
+       }
+       return uint(d)
+}
+
+func hexToInt2(h string) (uint, uint) {
+       if len(h) > 16 {
+               d, err := strconv.ParseUint(h[:16], 16, 64)
+               if err != nil {
+                       log.Fatal("Error while parsing %s to int: %s", h[16:], err)
+               }
+               d2, err := strconv.ParseUint(h[16:], 16, 64)
+               if err != nil {
+                       log.Fatal("Error while parsing %s to int: %s", h[16:], err)
+               }
+               return uint(d), uint(d2)
+       }
+
+       d, err := strconv.ParseUint(h, 16, 64)
+       if err != nil {
+               log.Fatal("Error while parsing %s to int: %s", h[16:], err)
+       }
+       return uint(d), 0
+}
+
+func hexToIP(h string) net.IP {
+       n, m := hexToInt2(h)
+       var ip net.IP
+       if m != 0 {
+               ip = make(net.IP, 16)
+               // TODO: Check if this depends on machine endianness?
+               binary.LittleEndian.PutUint32(ip, uint32(n>>32))
+               binary.LittleEndian.PutUint32(ip[4:], uint32(n))
+               binary.LittleEndian.PutUint32(ip[8:], uint32(m>>32))
+               binary.LittleEndian.PutUint32(ip[12:], uint32(m))
+       } else {
+               ip = make(net.IP, 4)
+               binary.LittleEndian.PutUint32(ip, uint32(n))
+       }
+       return ip
+}
+
+// Parse scans and retrieves the opened connections, from /proc/net/ files
+func Parse(proto string) ([]Entry, error) {
+       filename := core.ConcatStrings("/proc/net/", proto)
+       fd, err := os.Open(filename)
+       if err != nil {
+               return nil, err
+       }
+       defer fd.Close()
+
+       entries := make([]Entry, 0)
+       scanner := bufio.NewScanner(fd)
+       for lineno := 0; scanner.Scan(); lineno++ {
+               // skip column names
+               if lineno == 0 {
+                       continue
+               }
+
+               line := core.Trim(scanner.Text())
+               m := parser.FindStringSubmatch(line)
+               if m == nil {
+                       log.Warning("Could not parse netstat line from %s: %s", filename, line)
+                       continue
+               }
+
+               entries = append(entries, NewEntry(
+                       proto,
+                       hexToIP(m[1]),
+                       hexToInt(m[2]),
+                       hexToIP(m[3]),
+                       hexToInt(m[4]),
+                       decToInt(m[5]),
+                       decToInt(m[6]),
+               ))
+       }
+
+       return entries, nil
+}
diff --git a/daemon/opensnitchd-dinit b/daemon/opensnitchd-dinit
new file mode 100644 (file)
index 0000000..fdca2af
--- /dev/null
@@ -0,0 +1,8 @@
+# Application firewall OpenSnitch
+type = process
+command = /usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules
+restart = true
+smooth-recovery = yes
+restart-delay = 15
+stop-timeout = 10
+restart-limit-count = 0
diff --git a/daemon/opensnitchd-openrc b/daemon/opensnitchd-openrc
new file mode 100755 (executable)
index 0000000..85a7329
--- /dev/null
@@ -0,0 +1,36 @@
+#!/sbin/openrc-run
+# OpenSnitch firewall service
+
+depend() {
+    before net
+    after iptables ip6tables 
+    use logger
+    provide firewall
+}
+
+start_pre() {
+        /bin/mkdir -p /etc/opensnitchd/rules
+        /bin/chown -R root:root /etc/opensnitchd
+        /bin/chown root:root /var/log/opensnitchd.log
+        /bin/chmod -R 755 /etc/opensnitchd
+        /bin/chmod -R 0644 /etc/opensnitchd/rules
+        /bin/chmod 0600 /var/log/opensnitchd.log
+}
+
+start() {
+    ebegin "Starting application firewall"
+    # only if the verbose flag is not set (rc-service opensnitchd start -v)
+    if [ -z "$VERBOSE" ]; then
+        # redirect stdout and stderr to /dev/null
+        /usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules -log-file /var/log/opensnitchd.log > /dev/null 2>&1 &
+    else
+        /usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules -log-file /var/log/opensnitchd.log
+    fi
+    eend $?
+}
+
+stop() {
+    ebegin "Stopping application firewall"
+    /usr/bin/pkill -SIGINT opensnitchd 
+    eend $?
+}
diff --git a/daemon/opensnitchd.service b/daemon/opensnitchd.service
new file mode 100644 (file)
index 0000000..3f05fad
--- /dev/null
@@ -0,0 +1,15 @@
+[Unit]
+Description=Application firewall OpenSnitch
+Documentation=https://github.com/evilsocket/opensnitch/wiki
+
+[Service]
+Type=simple
+PermissionsStartOnly=true
+ExecStartPre=/bin/mkdir -p /etc/opensnitchd/rules
+ExecStart=/usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules
+Restart=always
+RestartSec=30
+TimeoutStopSec=10
+
+[Install]
+WantedBy=multi-user.target
diff --git a/daemon/procmon/activepids.go b/daemon/procmon/activepids.go
new file mode 100644 (file)
index 0000000..9b2009f
--- /dev/null
@@ -0,0 +1,90 @@
+package procmon
+
+import (
+       "io/ioutil"
+       "strconv"
+       "strings"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+type value struct {
+       Process *Process
+       //Starttime uniquely identifies a process, it is the 22nd value in /proc/<PID>/stat
+       //if another process starts with the same PID, it's Starttime will be unique
+       Starttime uint64
+}
+
+var (
+       activePids     = make(map[uint64]value)
+       activePidsLock = sync.RWMutex{}
+)
+
+//MonitorActivePids checks that each process in activePids
+//is still running and if not running (or another process with the same pid is running),
+//removes the pid from activePids
+func MonitorActivePids() {
+       for {
+               time.Sleep(time.Second)
+               activePidsLock.Lock()
+               for k, v := range activePids {
+                       data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.FormatUint(k, 10), "/stat"))
+                       if err != nil {
+                               //file does not exists, pid has quit
+                               delete(activePids, k)
+                               pidsCache.delete(int(k))
+                               continue
+                       }
+                       startTime, err := strconv.ParseInt(strings.Split(string(data), " ")[21], 10, 64)
+                       if err != nil {
+                               log.Error("Could not find or convert Starttime. This should never happen. Please report this incident to the Opensnitch developers: %v", err)
+                               delete(activePids, k)
+                               pidsCache.delete(int(k))
+                               continue
+                       }
+                       if uint64(startTime) != v.Starttime {
+                               //extremely unlikely: the original process has quit and another process
+                               //was started with the same PID - all this in less than 1 second
+                               log.Error("Same PID but different Starttime. Please report this incident to the Opensnitch developers.")
+                               delete(activePids, k)
+                               pidsCache.delete(int(k))
+                               continue
+                       }
+               }
+               activePidsLock.Unlock()
+       }
+}
+
+func findProcessInActivePidsCache(pid uint64) *Process {
+       activePidsLock.Lock()
+       defer activePidsLock.Unlock()
+       if value, ok := activePids[pid]; ok {
+               return value.Process
+       }
+       return nil
+}
+
+// AddToActivePidsCache adds the given pid to a list of known processes.
+func AddToActivePidsCache(pid uint64, proc *Process) {
+
+       data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.FormatUint(pid, 10), "/stat"))
+       if err != nil {
+               //most likely the process has quit by now
+               return
+       }
+       startTime, err2 := strconv.ParseInt(strings.Split(string(data), " ")[21], 10, 64)
+       if err2 != nil {
+               log.Error("Could not find or convert Starttime. This should never happen. Please report this incident to the Opensnitch developers: %v", err)
+               return
+       }
+
+       activePidsLock.Lock()
+       activePids[pid] = value{
+               Process:   proc,
+               Starttime: uint64(startTime),
+       }
+       activePidsLock.Unlock()
+}
diff --git a/daemon/procmon/activepids_test.go b/daemon/procmon/activepids_test.go
new file mode 100644 (file)
index 0000000..ccc2c44
--- /dev/null
@@ -0,0 +1,104 @@
+package procmon
+
+import (
+       "fmt"
+       "math/rand"
+       "os"
+       "os/exec"
+       "syscall"
+       "testing"
+       "time"
+)
+
+//TestMonitorActivePids starts helper processes, adds them to activePids
+//and then kills them and checks if monitorActivePids() removed the killed processes
+//from activePids
+func TestMonitorActivePids(t *testing.T) {
+
+       if os.Getenv("helperBinaryMode") == "on" {
+               //we are in the "helper binary" mode, we were started with helperCmd.Start() (see below)
+               //do nothing, just wait to be killed
+               time.Sleep(time.Second * 10)
+               os.Exit(1) //will never get here; but keep it here just in case
+       }
+
+       //we are in a normal "go test" mode
+       tmpDir := "/tmp/ostest_" + randString()
+       os.Mkdir(tmpDir, 0777)
+       fmt.Println("tmp dir", tmpDir)
+       defer os.RemoveAll(tmpDir)
+
+       go MonitorActivePids()
+
+       //build a "helper binary" with "go test -c -o /tmp/path" and put it into a tmp dir
+       helperBinaryPath := tmpDir + "/helper1"
+       goExecutable, _ := exec.LookPath("go")
+       cmd := exec.Command(goExecutable, "test", "-c", "-o", helperBinaryPath)
+       if err := cmd.Run(); err != nil {
+               t.Error("Error running go test -c", err)
+       }
+
+       var numberOfHelpers = 5
+       var helperProcs []*Process
+       //start helper binaries
+       for i := 0; i < numberOfHelpers; i++ {
+               var helperCmd *exec.Cmd
+               helperCmd = &exec.Cmd{
+                       Path: helperBinaryPath,
+                       Args: []string{helperBinaryPath},
+                       Env:  []string{"helperBinaryMode=on"},
+               }
+               if err := helperCmd.Start(); err != nil {
+                       t.Error("Error starting helper binary", err)
+               }
+               go func() {
+                       helperCmd.Wait() //must Wait(), otherwise the helper process becomes a zombie when kill()ed
+               }()
+
+               pid := helperCmd.Process.Pid
+               proc := NewProcess(pid, helperBinaryPath)
+               helperProcs = append(helperProcs, proc)
+               AddToActivePidsCache(uint64(pid), proc)
+       }
+       //sleep to make sure all processes started before we proceed
+       time.Sleep(time.Second * 1)
+       //make sure all PIDS are in the cache
+       for i := 0; i < numberOfHelpers; i++ {
+               proc := helperProcs[i]
+               pid := proc.ID
+               foundProc := findProcessInActivePidsCache(uint64(pid))
+               if foundProc == nil {
+                       t.Error("PID not found among active processes", pid)
+               }
+               if proc.Path != foundProc.Path || proc.ID != foundProc.ID {
+                       t.Error("PID or path doesn't match with the found process")
+               }
+       }
+       //kill all helpers except for one
+       for i := 0; i < numberOfHelpers-1; i++ {
+               if err := syscall.Kill(helperProcs[i].ID, syscall.SIGTERM); err != nil {
+                       t.Error("error in syscall.Kill", err)
+               }
+       }
+       //give the cache time to remove killed processes
+       time.Sleep(time.Second * 1)
+
+       //make sure only the alive process is in the cache
+       foundProc := findProcessInActivePidsCache(uint64(helperProcs[numberOfHelpers-1].ID))
+       if foundProc == nil {
+               t.Error("last alive PID is not found among active processes", foundProc)
+       }
+       if len(activePids) != 1 {
+               t.Error("more than 1 active PIDs left in cache")
+       }
+}
+
+func randString() string {
+       rand.Seed(time.Now().UnixNano())
+       var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+       b := make([]rune, 10)
+       for i := range b {
+               b[i] = letterRunes[rand.Intn(len(letterRunes))]
+       }
+       return string(b)
+}
diff --git a/daemon/procmon/audit/client.go b/daemon/procmon/audit/client.go
new file mode 100644 (file)
index 0000000..396cc06
--- /dev/null
@@ -0,0 +1,355 @@
+// Package audit reads auditd events from the builtin af_unix plugin, and parses
+// the messages in order to proactively monitor pids which make connections.
+// Once a connection is made and redirected to us via NFQUEUE, we
+// lookup the connection inode in /proc, and add the corresponding PID with all
+// the information of the process to a list of known PIDs.
+//
+// TODO: Prompt the user to allow/deny a connection/program as soon as it's
+// started.
+//
+// Requisities:
+// - install auditd and audispd-plugins
+// - enable af_unix plugin /etc/audisp/plugins.d/af_unix.conf (active = yes)
+// - auditctl -a always,exit -F arch=b64 -S socket,connect,execve -k opensnitchd
+// - increase /etc/audisp/audispd.conf q_depth if there're dropped events
+// - set write_logs to no if you don't need/want audit logs to be stored in the disk.
+//
+// read messages from the pipe to verify that it's working:
+// socat unix-connect:/var/run/audispd_events stdio
+//
+// Audit event fields:
+// https://github.com/linux-audit/audit-documentation/blob/master/specs/fields/field-dictionary.csv
+// Record types:
+// https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Audit_Record_Types.html
+//
+// Documentation:
+// https://github.com/linux-audit/audit-documentation
+package audit
+
+import (
+       "bufio"
+       "fmt"
+       "io"
+       "net"
+       "os"
+       "runtime"
+       "sort"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+// Event represents an audit event, which in our case can be an event of type
+// socket, execve, socketpair or connect.
+type Event struct {
+       Timestamp   string // audit(xxxxxxx:nnnn)
+       Serial      string
+       ProcName    string // comm
+       ProcPath    string // exe
+       ProcCmdLine string // proctitle
+       ProcDir     string // cwd
+       ProcMode    string // mode
+       TTY         string
+       Pid         int
+       UID         int
+       Gid         int
+       PPid        int
+       EUid        int
+       EGid        int
+       OUid        int
+       OGid        int
+       UserName    string // auid
+       DstHost     net.IP
+       DstPort     int
+       NetFamily   string // inet, inet6, local
+       Success     string
+       INode       int
+       Dev         string
+       Syscall     int
+       Exit        int
+       EventType   string
+       RawEvent    string
+       LastSeen    time.Time
+}
+
+// MaxEventAge is the maximum minutes an audit process can live without network activity.
+const (
+       MaxEventAge = int(10)
+)
+
+var (
+       // Lock holds a mutex
+       Lock   sync.RWMutex
+       ourPid = os.Getpid()
+       // cache of events
+       events            []*Event
+       eventsCleaner     *time.Ticker
+       eventsCleanerChan = (chan bool)(nil)
+       // TODO: EventChan is an output channel where incoming auditd events will be written.
+       // If a client opens it.
+       EventChan      = (chan Event)(nil)
+       eventsExitChan = (chan bool)(nil)
+       auditConn      net.Conn
+       // TODO: we may need arm arch
+       rule64      = []string{"exit,always", "-F", "arch=b64", "-F", fmt.Sprint("ppid!=", ourPid), "-F", fmt.Sprint("pid!=", ourPid), "-S", "socket,connect", "-k", "opensnitch"}
+       rule32      = []string{"exit,always", "-F", "arch=b32", "-F", fmt.Sprint("ppid!=", ourPid), "-F", fmt.Sprint("pid!=", ourPid), "-S", "socketcall", "-F", "a0=1", "-k", "opensnitch"}
+       audispdPath = "/var/run/audispd_events"
+)
+
+// OPENSNITCH_RULES_KEY is the mark we place on every event we are interested in.
+const (
+       OpensnitchRulesKey = "key=\"opensnitch\""
+)
+
+// GetEvents returns the list of processes which have opened a connection.
+func GetEvents() []*Event {
+       return events
+}
+
+// GetEventByPid returns an event given a pid.
+func GetEventByPid(pid int) *Event {
+       Lock.RLock()
+       defer Lock.RUnlock()
+
+       for _, event := range events {
+               if pid == event.Pid {
+                       return event
+               }
+       }
+
+       return nil
+}
+
+// sortEvents sorts received events by time and elapsed time since latest network activity.
+// newest PIDs will be placed on top of the list.
+func sortEvents() {
+       sort.Slice(events, func(i, j int) bool {
+               now := time.Now()
+               elapsedTimeT := now.Sub(events[i].LastSeen)
+               elapsedTimeU := now.Sub(events[j].LastSeen)
+               t := events[i].LastSeen.UnixNano()
+               u := events[j].LastSeen.UnixNano()
+               return t > u && elapsedTimeT < elapsedTimeU
+       })
+}
+
+// cleanOldEvents deletes the PIDs which do not exist or that are too old to
+// live.
+// We start searching from the oldest to the newest.
+// If the last network activity of a PID has been greater than MaxEventAge,
+// then it'll be deleted.
+func cleanOldEvents() {
+       Lock.Lock()
+       defer Lock.Unlock()
+
+       for n := len(events) - 1; n >= 0; n-- {
+               now := time.Now()
+               elapsedTime := now.Sub(events[n].LastSeen)
+               if int(elapsedTime.Minutes()) >= MaxEventAge {
+                       events = append(events[:n], events[n+1:]...)
+                       continue
+               }
+               if core.Exists(fmt.Sprint("/proc/", events[n].Pid)) == false {
+                       events = append(events[:n], events[n+1:]...)
+               }
+       }
+}
+
+func deleteEvent(pid int) {
+       for n := range events {
+               if events[n].Pid == pid || events[n].PPid == pid {
+                       deleteEventByIndex(n)
+                       break
+               }
+       }
+}
+
+func deleteEventByIndex(index int) {
+       Lock.Lock()
+       events = append(events[:index], events[index+1:]...)
+       Lock.Unlock()
+}
+
+// AddEvent adds new event to the list of PIDs which have generate network
+// activity.
+// If the PID is already in the list, the LastSeen field is updated, to keep
+// it alive.
+func AddEvent(aevent *Event) {
+       if aevent == nil {
+               return
+       }
+       Lock.Lock()
+       defer Lock.Unlock()
+
+       for n := 0; n < len(events); n++ {
+               if events[n].Pid == aevent.Pid && events[n].Syscall == aevent.Syscall {
+                       if aevent.ProcCmdLine != "" || (aevent.ProcCmdLine == events[n].ProcCmdLine) {
+                               events[n] = aevent
+                       }
+                       events[n].LastSeen = time.Now()
+
+                       sortEvents()
+                       return
+               }
+       }
+       aevent.LastSeen = time.Now()
+       events = append([]*Event{aevent}, events...)
+}
+
+// startEventsCleaner will review if the events in the cache need to be cleaned
+// every 5 minutes.
+func startEventsCleaner() {
+       for {
+               select {
+               case <-eventsCleanerChan:
+                       goto Exit
+               case <-eventsCleaner.C:
+                       cleanOldEvents()
+               }
+       }
+Exit:
+       log.Debug("audit: cleanerRoutine stopped")
+}
+
+func addRules() bool {
+       r64 := append([]string{"-A"}, rule64...)
+       r32 := append([]string{"-A"}, rule32...)
+       _, err64 := core.Exec("auditctl", r64)
+       _, err32 := core.Exec("auditctl", r32)
+       if err64 == nil && err32 == nil {
+               return true
+       }
+       log.Error("Error adding audit rule, err32=%v, err=%v", err32, err64)
+       return false
+}
+
+func configureSyscalls() {
+       // XXX: what about a i386 process running on a x86_64 system?
+       if runtime.GOARCH == "386" {
+               syscallSOCKET = "1"
+               syscallCONNECT = "3"
+               syscallSOCKETPAIR = "8"
+       }
+}
+
+func deleteRules() bool {
+       r64 := []string{"-D", "-k", "opensnitch"}
+       r32 := []string{"-D", "-k", "opensnitch"}
+       _, err64 := core.Exec("auditctl", r64)
+       _, err32 := core.Exec("auditctl", r32)
+       if err64 == nil && err32 == nil {
+               return true
+       }
+       log.Error("Error deleting audit rules, err32=%v, err64=%v", err32, err64)
+       return false
+}
+
+func checkRules() bool {
+       // TODO
+       return true
+}
+
+func checkStatus() bool {
+       // TODO
+       return true
+}
+
+// Reader reads events from audisd af_unix pipe plugin.
+// If the auditd daemon is stopped or restarted, the reader handle
+// is closed, so we need to restablished the connection.
+func Reader(r io.Reader, eventChan chan<- Event) {
+       if r == nil {
+               log.Error("Error reading auditd events. Is auditd running? is af_unix plugin enabled?")
+               return
+       }
+       reader := bufio.NewReader(r)
+       go startEventsCleaner()
+
+       for {
+               select {
+               case <-eventsExitChan:
+                       goto Exit
+               default:
+                       buf, _, err := reader.ReadLine()
+                       if err != nil {
+                               if err == io.EOF {
+                                       log.Error("AuditReader: auditd stopped, reconnecting in 30s %s", err)
+                                       if newReader, err := reconnect(); err == nil {
+                                               reader = bufio.NewReader(newReader)
+                                               log.Important("Auditd reconnected, continue reading")
+                                       }
+                                       continue
+                               }
+                               log.Warning("AuditReader: auditd error %s", err)
+                               break
+                       }
+
+                       parseEvent(string(buf[0:len(buf)]), eventChan)
+               }
+       }
+Exit:
+       log.Debug("audit.Reader() closed")
+}
+
+// StartChannel creates a channel to receive events from Audit.
+// Launch audit.Reader() in a goroutine:
+// go audit.Reader(c, (chan<- audit.Event)(audit.EventChan))
+func StartChannel() {
+       EventChan = make(chan Event, 0)
+}
+
+func reconnect() (net.Conn, error) {
+       deleteRules()
+       time.Sleep(30 * time.Second)
+       return connect()
+}
+
+func connect() (net.Conn, error) {
+       addRules()
+       // TODO: make the unix socket path configurable
+       return net.Dial("unix", audispdPath)
+}
+
+// Stop stops listening for events from auditd and delete the auditd rules.
+func Stop() {
+       if auditConn != nil {
+               if err := auditConn.Close(); err != nil {
+                       log.Warning("audit.Stop() error closing socket: %v", err)
+               }
+       }
+
+       if eventsCleaner != nil {
+               eventsCleaner.Stop()
+       }
+       if eventsExitChan != nil {
+               eventsExitChan <- true
+               close(eventsExitChan)
+       }
+       if eventsCleanerChan != nil {
+               eventsCleanerChan <- true
+               close(eventsCleanerChan)
+       }
+
+       deleteRules()
+       if EventChan != nil {
+               close(EventChan)
+       }
+}
+
+// Start makes a new connection to the audisp af_unix socket.
+func Start() (net.Conn, error) {
+       auditConn, err := connect()
+       if err != nil {
+               log.Error("auditd Start() connection error %v", err)
+               deleteRules()
+               return nil, err
+       }
+
+       configureSyscalls()
+       eventsCleaner = time.NewTicker(time.Minute * 5)
+       eventsCleanerChan = make(chan bool)
+       eventsExitChan = make(chan bool)
+       return auditConn, err
+}
diff --git a/daemon/procmon/audit/parse.go b/daemon/procmon/audit/parse.go
new file mode 100644 (file)
index 0000000..a666588
--- /dev/null
@@ -0,0 +1,298 @@
+package audit
+
+import (
+       "encoding/hex"
+       "fmt"
+       "net"
+       "regexp"
+       "strconv"
+       "strings"
+)
+
+var (
+       newEvent = false
+       netEvent = &Event{}
+
+       // RegExp for parse audit messages
+       // https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/security_guide/sec-understanding_audit_log_files
+       auditRE, _ = regexp.Compile(`([a-zA-Z0-9\-_]+)=([a-zA-Z0-9:'\-\/\"\.\,_\(\)]+)`)
+       rawEvent   = make(map[string]string)
+)
+
+// amd64 syscalls definition
+// if the platform is not amd64, it's redefined on Start()
+var (
+       syscallSOCKET     = "41"
+       syscallCONNECT    = "42"
+       syscallSOCKETPAIR = "53"
+       syscallEXECVE     = "59"
+       syscallSOCKETCALL = "102"
+)
+
+// /usr/include/x86_64-linux-gnu/bits/socket_type.h
+const (
+       sockSTREAM    = "1"
+       sockDGRAM     = "2"
+       sockRAW       = "3"
+       sockSEQPACKET = "5"
+       sockPACKET    = "10"
+
+       // /usr/include/x86_64-linux-gnu/bits/socket.h
+       pfUNSPEC = "0"
+       pfLOCAL  = "1" // PF_UNIX
+       pfINET   = "2"
+       pfINET6  = "10"
+
+       // /etc/protocols
+       protoIP  = "0"
+       protoTCP = "6"
+       protoUDP = "17"
+)
+
+// https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Audit_Record_Types.html
+const (
+       AuditTypePROCTITLE  = "type=PROCTITLE"
+       AuditTypeCWD        = "type=CWD"
+       AuditTypePATH       = "type=PATH"
+       AuditTypeEXECVE     = "type=EXECVE"
+       AuditTypeSOCKADDR   = "type=SOCKADDR"
+       AuditTypeSOCKETCALL = "type=SOCKETCALL"
+       AuditTypeEOE        = "type=EOE"
+)
+
+var (
+       syscallSOCKETstr     = fmt.Sprint("syscall=", syscallSOCKET)
+       syscallCONNECTstr    = fmt.Sprint("syscall=", syscallCONNECT)
+       syscallSOCKETPAIRstr = fmt.Sprint("syscall=", syscallSOCKETPAIR)
+       syscallEXECVEstr     = fmt.Sprint("syscall=", syscallEXECVE)
+       syscallSOCKETCALLstr = fmt.Sprint("syscall=", syscallSOCKETCALL)
+)
+
+// parseNetLine parses a SOCKADDR message type of the form:
+// saddr string: inet6 host:2001:4860:4860::8888 serv:53
+func parseNetLine(line string, decode bool) (family string, dstHost net.IP, dstPort int) {
+
+       // 0:4 - type
+       // 4:8 - port
+       // 8:16 - ip
+       switch family := line[0:4]; family {
+       // local
+       // case "0100":
+       // ipv4
+       case "0200":
+               octet2 := decodeString(line[4:8])
+               octet := decodeString(line[8:16])
+               host := fmt.Sprint(octet[0], ".", octet[1], ".", octet[2], ".", octet[3])
+               fmt.Printf("dest ip: %s -- %s:%s\n", line[4:8], octet2, host)
+               // ipv6
+               //case "0A00":
+       }
+
+       if decode == true {
+               line = decodeString(line)
+       }
+       pieces := strings.Split(line, " ")
+       family = pieces[0]
+
+       if family[:4] != "inet" {
+               return family, dstHost, 0
+       }
+
+       if len(pieces) > 1 && pieces[1][:5] == "host:" {
+               dstHost = net.ParseIP(strings.Split(pieces[1], "host:")[1])
+       }
+       if len(pieces) > 2 && pieces[2][:5] == "serv:" {
+               _dstPort, err := strconv.Atoi(strings.Split(line, "serv:")[1])
+               if err != nil {
+                       dstPort = -1
+               } else {
+                       dstPort = _dstPort
+               }
+       }
+
+       return family, dstHost, dstPort
+}
+
+// decodeString will try to decode a string encoded in hexadecimal.
+// If the string can not be decoded, the original string will be returned.
+// In that case, usually it means that it's a non-encoded string.
+func decodeString(s string) string {
+       decoded, err := hex.DecodeString(s)
+       if err != nil {
+               return s
+       }
+       return fmt.Sprintf("%s", decoded)
+}
+
+// extractFields parsed an audit raw message, and extracts all the fields.
+func extractFields(rawMessage string, newEvent *map[string]string) {
+       Lock.Lock()
+       defer Lock.Unlock()
+
+       if auditRE == nil {
+               newEvent = nil
+               return
+       }
+       fieldList := auditRE.FindAllStringSubmatch(rawMessage, -1)
+       if fieldList == nil {
+               newEvent = nil
+               return
+       }
+       for _, field := range fieldList {
+               (*newEvent)[field[1]] = field[2]
+       }
+}
+
+// populateEvent populates our Event from a raw parsed message.
+func populateEvent(aevent *Event, eventFields *map[string]string) *Event {
+       if aevent == nil {
+               return nil
+       }
+       Lock.Lock()
+       defer Lock.Unlock()
+
+       for k, v := range *eventFields {
+               switch k {
+               //case "a0":
+               //case "a1":
+               //case "a2":
+               case "fam":
+                       if v == "local" {
+                               return nil
+                       }
+                       aevent.NetFamily = v
+               case "lport":
+                       aevent.DstPort, _ = strconv.Atoi(v)
+               // TODO
+               /*case "addr":
+                       fmt.Println("addr: ", v)
+               case "daddr":
+                       fmt.Println("daddr: ", v)
+               case "laddr":
+                       aevent.DstHost = net.ParseIP(v)
+               case "saddr":
+               parseNetLine(v, true)
+               fmt.Println("saddr:", v)
+               */
+               case "exe":
+                       aevent.ProcPath = strings.Trim(decodeString(v), "\"")
+               case "comm":
+                       aevent.ProcName = strings.Trim(decodeString(v), "\"")
+               // proctitle may be truncated to 128 characters, so don't rely on it, parse /proc/<pid>/instead
+               //case "proctitle":
+               //      aevent.ProcCmdLine = strings.Trim(decodeString(v), "\"")
+               case "tty":
+                       aevent.TTY = v
+               case "pid":
+                       aevent.Pid, _ = strconv.Atoi(v)
+               case "ppid":
+                       aevent.PPid, _ = strconv.Atoi(v)
+               case "uid":
+                       aevent.UID, _ = strconv.Atoi(v)
+               case "gid":
+                       aevent.Gid, _ = strconv.Atoi(v)
+               case "success":
+                       aevent.Success = v
+               case "cwd":
+                       aevent.ProcDir = strings.Trim(decodeString(v), "\"")
+               case "inode":
+                       aevent.INode, _ = strconv.Atoi(v)
+               case "dev":
+                       aevent.Dev = v
+               case "mode":
+                       aevent.ProcMode = v
+               case "ouid":
+                       aevent.OUid, _ = strconv.Atoi(v)
+               case "ogid":
+                       aevent.OGid, _ = strconv.Atoi(v)
+               case "syscall":
+                       aevent.Syscall, _ = strconv.Atoi(v)
+               case "exit":
+                       aevent.Exit, _ = strconv.Atoi(v)
+               case "type":
+                       aevent.EventType = v
+               case "msg":
+                       parts := strings.Split(v[6:], ":")
+                       aevent.Timestamp = parts[0]
+                       aevent.Serial = parts[1][:len(parts[1])-1]
+               }
+       }
+
+       return aevent
+}
+
+// parseEvent parses an auditd event, discards the unwanted ones, and adds
+// the ones we're interested in to an array.
+// We're only interested in the socket,socketpair,connect and execve syscalls.
+// Events from us are excluded.
+//
+// When we received an event, we parse and add it to the list as soon as we can.
+// If the next messages of the set have additional information, we update the
+// event.
+func parseEvent(rawMessage string, eventChan chan<- Event) {
+       if newEvent == false && strings.Index(rawMessage, OpensnitchRulesKey) == -1 {
+               return
+       }
+
+       aEvent := make(map[string]string)
+       if strings.Index(rawMessage, syscallSOCKETstr) != -1 ||
+               strings.Index(rawMessage, syscallCONNECTstr) != -1 ||
+               strings.Index(rawMessage, syscallSOCKETPAIRstr) != -1 ||
+               strings.Index(rawMessage, syscallEXECVEstr) != -1 ||
+               strings.Index(rawMessage, syscallSOCKETCALLstr) != -1 {
+
+               extractFields(rawMessage, &aEvent)
+               if aEvent == nil {
+                       return
+               }
+               newEvent = true
+               netEvent = &Event{}
+               netEvent = populateEvent(netEvent, &aEvent)
+               AddEvent(netEvent)
+       } else if newEvent == true && strings.Index(rawMessage, AuditTypePROCTITLE) != -1 {
+               extractFields(rawMessage, &aEvent)
+               if aEvent == nil {
+                       return
+               }
+               netEvent = populateEvent(netEvent, &aEvent)
+               AddEvent(netEvent)
+       } else if newEvent == true && strings.Index(rawMessage, AuditTypeCWD) != -1 {
+               extractFields(rawMessage, &aEvent)
+               if aEvent == nil {
+                       return
+               }
+               netEvent = populateEvent(netEvent, &aEvent)
+               AddEvent(netEvent)
+       } else if newEvent == true && strings.Index(rawMessage, AuditTypeEXECVE) != -1 {
+               extractFields(rawMessage, &aEvent)
+               if aEvent == nil {
+                       return
+               }
+               netEvent = populateEvent(netEvent, &aEvent)
+               AddEvent(netEvent)
+       } else if newEvent == true && strings.Index(rawMessage, AuditTypePATH) != -1 {
+               extractFields(rawMessage, &aEvent)
+               if aEvent == nil {
+                       return
+               }
+               netEvent = populateEvent(netEvent, &aEvent)
+               AddEvent(netEvent)
+       } else if newEvent == true && strings.Index(rawMessage, AuditTypeSOCKADDR) != -1 {
+               extractFields(rawMessage, &aEvent)
+               if aEvent == nil {
+                       return
+               }
+
+               netEvent = populateEvent(netEvent, &aEvent)
+               AddEvent(netEvent)
+               if EventChan != nil {
+                       eventChan <- *netEvent
+               }
+       } else if newEvent == true && strings.Index(rawMessage, AuditTypeEOE) != -1 {
+               newEvent = false
+               AddEvent(netEvent)
+               if EventChan != nil {
+                       eventChan <- *netEvent
+               }
+       }
+}
diff --git a/daemon/procmon/cache.go b/daemon/procmon/cache.go
new file mode 100644 (file)
index 0000000..f284ff4
--- /dev/null
@@ -0,0 +1,339 @@
+package procmon
+
+import (
+       "os"
+       "sort"
+       "strconv"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+)
+
+// InodeItem represents an item of the InodesCache.
+type InodeItem struct {
+       FdPath   string
+       LastSeen int64
+       Pid      int
+
+       sync.RWMutex
+}
+
+// ProcItem represents an item of the pidsCache
+type ProcItem struct {
+       FdPath      string
+       Descriptors []string
+       LastSeen    int64
+       Pid         int
+
+       sync.RWMutex
+}
+
+// CacheProcs holds the cache of processes that have established connections.
+type CacheProcs struct {
+       items []*ProcItem
+       sync.RWMutex
+}
+
+// CacheInodes holds the cache of Inodes.
+// The key is formed as follow:
+// inode+srcip+srcport+dstip+dstport
+type CacheInodes struct {
+       items map[string]*InodeItem
+       sync.RWMutex
+}
+
+var (
+       // cache of inodes, which help to not iterate over all the pidsCache and
+       // descriptors of /proc/<pid>/fd/
+       // 15-50us vs 50-80ms
+       // we hit this cache when:
+       // - we've blocked a connection and the process retries it several times until it gives up,
+       // - or when a process timeouts connecting to an IP/domain and it retries it again,
+       // - or when a process resolves a domain and then connects to the IP.
+       inodesCache = NewCacheOfInodes()
+       maxTTL      = 3 // maximum 3 minutes of inactivity in cache. Really rare, usually they lasts less than a minute.
+
+       // 2nd cache of already known running pids, which also saves time by
+       // iterating only over a few pids' descriptors, (30us-20ms vs. 50-80ms)
+       // since it's more likely that most of the connections will be made by the
+       // same (running) processes.
+       // The cache is ordered by time, placing in the first places those PIDs with
+       // active connections.
+       pidsCache            CacheProcs
+       pidsDescriptorsCache = make(map[int][]string)
+
+       cacheTicker = time.NewTicker(2 * time.Minute)
+)
+
+// CacheCleanerTask checks periodically if the inodes in the cache must be removed.
+func CacheCleanerTask() {
+       for {
+               select {
+               case <-cacheTicker.C:
+                       inodesCache.cleanup()
+               }
+       }
+}
+
+// NewCacheOfInodes returns a new cache for inodes.
+func NewCacheOfInodes() *CacheInodes {
+       return &CacheInodes{
+               items: make(map[string]*InodeItem),
+       }
+}
+
+//******************************************************************************
+// items of the caches.
+
+func (i *InodeItem) updateTime() {
+       i.Lock()
+       i.LastSeen = time.Now().UnixNano()
+       i.Unlock()
+}
+
+func (i *InodeItem) getTime() int64 {
+       i.RLock()
+       defer i.RUnlock()
+       return i.LastSeen
+}
+
+func (p *ProcItem) updateTime() {
+       p.Lock()
+       p.LastSeen = time.Now().UnixNano()
+       p.Unlock()
+}
+
+func (p *ProcItem) updateDescriptors(descriptors []string) {
+       p.Lock()
+       p.Descriptors = descriptors
+       p.Unlock()
+}
+
+//******************************************************************************
+// cache of processes
+
+func (c *CacheProcs) add(fdPath string, fdList []string, pid int) {
+       c.Lock()
+       defer c.Unlock()
+       for n := range c.items {
+               item := c.items[n]
+               if item == nil {
+                       continue
+               }
+               if item.Pid == pid {
+                       item.updateTime()
+                       return
+               }
+       }
+
+       procItem := &ProcItem{
+               Pid:         pid,
+               FdPath:      fdPath,
+               Descriptors: fdList,
+               LastSeen:    time.Now().UnixNano(),
+       }
+
+       c.setItems([]*ProcItem{procItem}, c.items)
+}
+
+func (c *CacheProcs) sort(pid int) {
+       item := c.getItem(0)
+       if item != nil && item.Pid == pid {
+               return
+       }
+       c.RLock()
+       defer c.RUnlock()
+
+       sort.Slice(c.items, func(i, j int) bool {
+               t := c.items[i].LastSeen
+               u := c.items[j].LastSeen
+               return t > u || t == u
+       })
+}
+
+func (c *CacheProcs) delete(pid int) {
+       c.Lock()
+       defer c.Unlock()
+
+       for n, procItem := range c.items {
+               if procItem.Pid == pid {
+                       c.deleteItem(n)
+                       inodesCache.delete(pid)
+                       break
+               }
+       }
+}
+
+func (c *CacheProcs) deleteItem(pos int) {
+       nItems := len(c.items)
+       if pos < nItems {
+               c.setItems(c.items[:pos], c.items[pos+1:])
+       }
+}
+
+func (c *CacheProcs) setItems(newItems []*ProcItem, oldItems []*ProcItem) {
+       c.items = append(newItems, oldItems...)
+}
+
+func (c *CacheProcs) getItem(index int) *ProcItem {
+       c.RLock()
+       defer c.RUnlock()
+
+       if index >= len(c.items) {
+               return nil
+       }
+
+       return c.items[index]
+}
+
+func (c *CacheProcs) getItems() []*ProcItem {
+       return c.items
+}
+
+func (c *CacheProcs) countItems() int {
+       c.RLock()
+       defer c.RUnlock()
+
+       return len(c.items)
+}
+
+// loop over the processes that have generated connections
+func (c *CacheProcs) getPid(inode int, inodeKey string, expect string) (int, int) {
+       c.Lock()
+       defer c.Unlock()
+
+       for n, procItem := range c.items {
+               if procItem == nil {
+                       continue
+               }
+
+               if idxDesc, _ := getPidDescriptorsFromCache(procItem.FdPath, inodeKey, expect, &procItem.Descriptors, procItem.Pid); idxDesc != -1 {
+                       procItem.updateTime()
+                       return procItem.Pid, n
+               }
+
+               descriptors := lookupPidDescriptors(procItem.FdPath, procItem.Pid)
+               if descriptors == nil {
+                       c.deleteItem(n)
+                       continue
+               }
+
+               procItem.updateDescriptors(descriptors)
+               if idxDesc, _ := getPidDescriptorsFromCache(procItem.FdPath, inodeKey, expect, &descriptors, procItem.Pid); idxDesc != -1 {
+                       procItem.updateTime()
+                       return procItem.Pid, n
+               }
+       }
+
+       return -1, -1
+}
+
+//******************************************************************************
+// cache of inodes
+
+func (i *CacheInodes) add(key, descLink string, pid int) {
+       i.Lock()
+       defer i.Unlock()
+
+       if descLink == "" {
+               descLink = core.ConcatStrings("/proc/", strconv.Itoa(pid), "/exe")
+       }
+       i.items[key] = &InodeItem{
+               FdPath:   descLink,
+               Pid:      pid,
+               LastSeen: time.Now().UnixNano(),
+       }
+}
+
+func (i *CacheInodes) delete(pid int) {
+       i.Lock()
+       defer i.Unlock()
+
+       for k, inodeItem := range i.items {
+               if inodeItem.Pid == pid {
+                       delete(i.items, k)
+               }
+       }
+}
+
+func (i *CacheInodes) getPid(inodeKey string) int {
+       if item, ok := i.isInCache(inodeKey); ok {
+               // sometimes the process may have disappeared at this point
+               if _, err := os.Lstat(item.FdPath); err == nil {
+                       item.updateTime()
+                       return item.Pid
+               }
+               pidsCache.delete(item.Pid)
+               i.delItem(inodeKey)
+       }
+
+       return -1
+}
+
+func (i *CacheInodes) delItem(inodeKey string) {
+       i.Lock()
+       defer i.Unlock()
+       delete(i.items, inodeKey)
+}
+
+func (i *CacheInodes) getItem(inodeKey string) *InodeItem {
+       i.RLock()
+       defer i.RUnlock()
+
+       return i.items[inodeKey]
+}
+
+func (i *CacheInodes) getItems() map[string]*InodeItem {
+       i.RLock()
+       defer i.RUnlock()
+
+       return i.items
+}
+
+func (i *CacheInodes) isInCache(inodeKey string) (*InodeItem, bool) {
+       i.RLock()
+       defer i.RUnlock()
+
+       if item, found := i.items[inodeKey]; found {
+               return item, true
+       }
+       return nil, false
+}
+
+func (i *CacheInodes) cleanup() {
+       now := time.Now()
+       i.Lock()
+       defer i.Unlock()
+       for k := range i.items {
+               if i.items[k] == nil {
+                       continue
+               }
+               lastSeen := now.Sub(
+                       time.Unix(0, i.items[k].getTime()),
+               )
+               if core.Exists(i.items[k].FdPath) == false || int(lastSeen.Minutes()) > maxTTL {
+                       delete(i.items, k)
+               }
+       }
+}
+
+func getPidDescriptorsFromCache(fdPath, inodeKey, expect string, descriptors *[]string, pid int) (int, *[]string) {
+       for fdIdx := 0; fdIdx < len(*descriptors); fdIdx++ {
+               descLink := core.ConcatStrings(fdPath, (*descriptors)[fdIdx])
+               if link, err := os.Readlink(descLink); err == nil && link == expect {
+                       if fdIdx > 0 {
+                               // reordering helps to reduce look up times by a factor of 10.
+                               fd := (*descriptors)[fdIdx]
+                               *descriptors = append((*descriptors)[:fdIdx], (*descriptors)[fdIdx+1:]...)
+                               *descriptors = append([]string{fd}, *descriptors...)
+                       }
+                       if _, ok := inodesCache.isInCache(inodeKey); ok {
+                               inodesCache.add(inodeKey, descLink, pid)
+                       }
+                       return fdIdx, descriptors
+               }
+       }
+
+       return -1, descriptors
+}
diff --git a/daemon/procmon/cache_test.go b/daemon/procmon/cache_test.go
new file mode 100644 (file)
index 0000000..5a7cd17
--- /dev/null
@@ -0,0 +1,103 @@
+package procmon
+
+import (
+       "fmt"
+       "testing"
+       "time"
+)
+
+func TestCacheProcs(t *testing.T) {
+       fdList := []string{"0", "1", "2"}
+       pidsCache.add(fmt.Sprint("/proc/", myPid, "/fd/"), fdList, myPid)
+       t.Log("Pids in cache: ", pidsCache.countItems())
+
+       t.Run("Test addProcEntry", func(t *testing.T) {
+               if pidsCache.countItems() != 1 {
+                       t.Error("pidsCache should be 1")
+               }
+       })
+
+       oldPid := pidsCache.getItem(0)
+       pidsCache.add(fmt.Sprint("/proc/", myPid, "/fd/"), fdList, myPid)
+       t.Run("Test addProcEntry update", func(t *testing.T) {
+               if pidsCache.countItems() != 1 {
+                       t.Error("pidsCache should still be 1!", pidsCache)
+               }
+               oldTime := time.Unix(0, oldPid.LastSeen)
+               newTime := time.Unix(0, pidsCache.getItem(0).LastSeen)
+               if oldTime.Equal(newTime) == false {
+                       t.Error("pidsCache, time not updated: ", oldTime, newTime)
+               }
+       })
+
+       pidsCache.add("/proc/2/fd", fdList, 2)
+       pidsCache.delete(2)
+       t.Run("Test deleteProcEntry", func(t *testing.T) {
+               if pidsCache.countItems() != 1 {
+                       t.Error("pidsCache should be 1:", pidsCache.countItems())
+               }
+       })
+
+       pid, _ := pidsCache.getPid(0, "", "/dev/null")
+       t.Run("Test getPidFromCache", func(t *testing.T) {
+               if pid != myPid {
+                       t.Error("pid not found in cache", pidsCache.countItems())
+               }
+       })
+
+       // should not crash, and the number of items should still be 1
+       pidsCache.deleteItem(1)
+       t.Run("Test deleteItem check bounds", func(t *testing.T) {
+               if pidsCache.countItems() != 1 {
+                       t.Error("deleteItem check bounds error", pidsCache.countItems())
+               }
+       })
+
+       pidsCache.deleteItem(0)
+       t.Run("Test deleteItem", func(t *testing.T) {
+               if pidsCache.countItems() != 0 {
+                       t.Error("deleteItem error", pidsCache.countItems())
+               }
+       })
+       t.Log("items in cache:", pidsCache.countItems())
+
+       // the key of an inodeCache entry is formed as: inodeNumer + srcIP + srcPort + dstIP + dstPort
+       inodeKey := "000000000127.0.0.144444127.0.0.153"
+       // add() expects a path to the inode fd (/proc/<pid>/fd/12345), but as getPid() will check the path in order to retrieve the pid,
+       // we just set it to "" and it'll use /proc/<pid>/exe
+       inodesCache.add(inodeKey, "", myPid)
+       t.Run("Test addInodeEntry", func(t *testing.T) {
+               if _, found := inodesCache.items[inodeKey]; !found {
+                       t.Error("inodesCache, inode not added:", len(inodesCache.items), inodesCache.items)
+               }
+       })
+
+       pid = inodesCache.getPid(inodeKey)
+       t.Run("Test getPidByInodeFromCache", func(t *testing.T) {
+               if pid != myPid {
+                       t.Error("inode not found in cache", pid, inodeKey, len(inodesCache.items), inodesCache.items)
+               }
+       })
+
+       // should delete all inodes of a pid
+       inodesCache.delete(myPid)
+       t.Run("Test deleteInodeEntry", func(t *testing.T) {
+               if _, found := inodesCache.items[inodeKey]; found {
+                       t.Error("inodesCache, key found in cache but it should not exist", inodeKey, len(inodesCache.items), inodesCache.items)
+               }
+       })
+}
+
+// Test getPidDescriptorsFromCache descriptors (inodes) reordering.
+// When an inode (descriptor) is found, if it's pushed to the top of the list,
+// the next time we look for it will cost -10x.
+// Without reordering, the inode 0 will always be found on the 10th position,
+// taking an average of 100us instead of 30.
+// Benchmark results with reordering: ~5600ns/op, without: ~56000ns/op.
+func BenchmarkGetPid(b *testing.B) {
+       fdList := []string{"10", "9", "8", "7", "6", "5", "4", "3", "2", "1", "0"}
+       pidsCache.add(fmt.Sprint("/proc/", myPid, "/fd/"), fdList, myPid)
+       for i := 0; i < b.N; i++ {
+               pidsCache.getPid(0, "", "/dev/null")
+       }
+}
diff --git a/daemon/procmon/details.go b/daemon/procmon/details.go
new file mode 100644 (file)
index 0000000..a0df194
--- /dev/null
@@ -0,0 +1,313 @@
+package procmon
+
+import (
+       "bufio"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "regexp"
+       "strconv"
+       "strings"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/dns"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/netlink"
+)
+
+var socketsRegex, _ = regexp.Compile(`socket:\[([0-9]+)\]`)
+
+// GetInfo collects information of a process.
+func (p *Process) GetInfo() error {
+       if os.Getpid() == p.ID {
+               return nil
+       }
+       // if the PID dir doesn't exist, the process may have exited or be a kernel connection
+       // XXX: can a kernel connection exist without an entry in ProcFS?
+       if p.Path == "" && p.IsAlive() == false {
+               log.Debug("PID can't be read /proc/ %d %s", p.ID, p.Comm)
+
+               // The Comm field shouldn't be empty if the proc monitor method is ebpf or audit.
+               // If it's proc and the corresponding entry doesn't exist, there's nothing we can
+               // do to inform the user about this process.
+               if p.Comm == "" {
+                       return fmt.Errorf("Unable to get process information")
+               }
+       }
+       p.ReadCmdline()
+       p.ReadComm()
+       p.ReadCwd()
+
+       if err := p.ReadPath(); err != nil {
+               log.Error("GetInfo() path can't be read")
+               return err
+       }
+       p.ReadEnv()
+
+       return nil
+}
+
+// GetExtraInfo collects information of a process.
+func (p *Process) GetExtraInfo() error {
+       p.ReadEnv()
+       p.readDescriptors()
+       p.readIOStats()
+       p.readStatus()
+
+       return nil
+}
+
+// ReadComm reads the comm name from ProcFS /proc/<pid>/comm
+func (p *Process) ReadComm() error {
+       if p.Comm != "" {
+               return nil
+       }
+       data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/comm"))
+       if err != nil {
+               return err
+       }
+       p.Comm = core.Trim(string(data))
+       return nil
+}
+
+// ReadCwd reads the current working directory name from ProcFS /proc/<pid>/cwd
+func (p *Process) ReadCwd() error {
+       if p.CWD != "" {
+               return nil
+       }
+       link, err := os.Readlink(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/cwd"))
+       if err != nil {
+               return err
+       }
+       p.CWD = link
+       return nil
+}
+
+// ReadEnv reads and parses the environment variables of a process.
+func (p *Process) ReadEnv() {
+       data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/environ"))
+       if err != nil {
+               return
+       }
+       for _, s := range strings.Split(string(data), "\x00") {
+               parts := strings.SplitN(core.Trim(s), "=", 2)
+               if parts != nil && len(parts) == 2 {
+                       key := core.Trim(parts[0])
+                       val := core.Trim(parts[1])
+                       p.Env[key] = val
+               }
+       }
+}
+
+// ReadPath reads the symbolic link that /proc/<pid>/exe points to.
+// Note 1: this link might not exist on the root filesystem, it might
+// have been executed from a container, so the real path would be:
+// /proc/<pid>/root/<path that 'exe' points to>
+//
+// Note 2:
+// There're at least 3 things that a (regular) kernel connection meets
+// from userspace POV:
+// - /proc/<pid>/cmdline and /proc/<pid>/maps empty
+// - /proc/<pid>/exe can't be read
+func (p *Process) ReadPath() error {
+       // avoid rereading the path
+       if p.Path != "" && core.IsAbsPath(p.Path) {
+               return nil
+       }
+       defer func() {
+               if p.Path == "" {
+                       // determine if this process might be of a kernel task.
+                       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/maps")); err == nil && len(data) == 0 {
+                               p.Path = "Kernel connection"
+                               p.Args = append(p.Args, p.Comm)
+                               return
+                       }
+                       p.Path = p.Comm
+               }
+       }()
+
+       linkName := core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/exe")
+       if _, err := os.Lstat(linkName); err != nil {
+               return err
+       }
+
+       // FIXME: this reading can give error: file name too long
+       link, err := os.Readlink(linkName)
+       if err != nil {
+               return err
+       }
+       p.SetPath(link)
+       return nil
+}
+
+// SetPath sets the path of the process, and fixes it if it's needed.
+func (p *Process) SetPath(path string) {
+       p.Path = path
+       p.CleanPath()
+}
+
+// ReadCmdline reads the cmdline of the process from ProcFS /proc/<pid>/cmdline
+// This file may be empty if the process is of a kernel task.
+// It can also be empty for short-lived processes.
+func (p *Process) ReadCmdline() {
+       if len(p.Args) > 0 {
+               return
+       }
+       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/cmdline")); err == nil {
+               if len(data) == 0 {
+                       return
+               }
+               for i, b := range data {
+                       if b == 0x00 {
+                               data[i] = byte(' ')
+                       }
+               }
+
+               args := strings.Split(string(data), " ")
+               for _, arg := range args {
+                       arg = core.Trim(arg)
+                       if arg != "" {
+                               p.Args = append(p.Args, arg)
+                       }
+               }
+       }
+       p.CleanArgs()
+}
+
+// CleanArgs applies fixes on the cmdline arguments.
+// - AppImages cmdline reports the execuable launched as /proc/self/exe,
+//   instead of the actual path to the binary.
+func (p *Process) CleanArgs() {
+       if len(p.Args) > 0 && p.Args[0] == ProcSelf {
+               p.Args[0] = p.Path
+       }
+}
+
+func (p *Process) readDescriptors() {
+       f, err := os.Open(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/fd/"))
+       if err != nil {
+               return
+       }
+       fDesc, err := f.Readdir(-1)
+       f.Close()
+       p.Descriptors = nil
+
+       for _, fd := range fDesc {
+               tempFd := &procDescriptors{
+                       Name: fd.Name(),
+               }
+               if link, err := os.Readlink(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/fd/", fd.Name())); err == nil {
+                       tempFd.SymLink = link
+                       socket := socketsRegex.FindStringSubmatch(link)
+                       if len(socket) > 0 {
+                               socketInfo, err := netlink.GetSocketInfoByInode(socket[1])
+                               if err == nil {
+                                       tempFd.SymLink = fmt.Sprintf("socket:[%s] - %d:%s -> %s:%d, state: %s", fd.Name(),
+                                               socketInfo.ID.SourcePort,
+                                               socketInfo.ID.Source.String(),
+                                               dns.HostOr(socketInfo.ID.Destination, socketInfo.ID.Destination.String()),
+                                               socketInfo.ID.DestinationPort,
+                                               netlink.TCPStatesMap[socketInfo.State])
+                               }
+                       }
+
+                       if linkInfo, err := os.Lstat(link); err == nil {
+                               tempFd.Size = linkInfo.Size()
+                               tempFd.ModTime = linkInfo.ModTime()
+                       }
+               }
+               p.Descriptors = append(p.Descriptors, tempFd)
+       }
+}
+
+func (p *Process) readIOStats() {
+       f, err := os.Open(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/io"))
+       if err != nil {
+               return
+       }
+       defer f.Close()
+
+       p.IOStats = &procIOstats{}
+
+       scanner := bufio.NewScanner(f)
+       for scanner.Scan() {
+               s := strings.Split(scanner.Text(), " ")
+               switch s[0] {
+               case "rchar:":
+                       p.IOStats.RChar, _ = strconv.ParseInt(s[1], 10, 64)
+               case "wchar:":
+                       p.IOStats.WChar, _ = strconv.ParseInt(s[1], 10, 64)
+               case "syscr:":
+                       p.IOStats.SyscallRead, _ = strconv.ParseInt(s[1], 10, 64)
+               case "syscw:":
+                       p.IOStats.SyscallWrite, _ = strconv.ParseInt(s[1], 10, 64)
+               case "read_bytes:":
+                       p.IOStats.ReadBytes, _ = strconv.ParseInt(s[1], 10, 64)
+               case "write_bytes:":
+                       p.IOStats.WriteBytes, _ = strconv.ParseInt(s[1], 10, 64)
+               }
+       }
+}
+
+func (p *Process) readStatus() {
+       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/status")); err == nil {
+               p.Status = string(data)
+       }
+       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/stat")); err == nil {
+               p.Stat = string(data)
+       }
+       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/stack")); err == nil {
+               p.Stack = string(data)
+       }
+       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/maps")); err == nil {
+               p.Maps = string(data)
+       }
+       if data, err := ioutil.ReadFile(core.ConcatStrings("/proc/", strconv.Itoa(p.ID), "/statm")); err == nil {
+               p.Statm = &procStatm{}
+               fmt.Sscanf(string(data), "%d %d %d %d %d %d %d", &p.Statm.Size, &p.Statm.Resident, &p.Statm.Shared, &p.Statm.Text, &p.Statm.Lib, &p.Statm.Data, &p.Statm.Dt)
+       }
+}
+
+// CleanPath applies fixes on the path to the binary:
+// - Remove extra characters from the link that it points to.
+//   When a running process is deleted, the symlink has the bytes " (deleted")
+//   appended to the link.
+// - If the path is /proc/self/exe, resolve the symlink that it points to.
+func (p *Process) CleanPath() {
+
+       // Sometimes the path to the binary reported is the symbolic link of the process itself.
+       // This is not useful to the user, and besides it's a generic path that can represent
+       // to any process.
+       // Therefore we cannot use /proc/self/exe directly, because it resolves to our own process.
+       if strings.HasPrefix(p.Path, ProcSelf) {
+               if link, err := os.Readlink(core.ConcatStrings(ProcSelf, "/exe")); err == nil {
+                       p.Path = link
+                       return
+               }
+
+               if len(p.Args) > 0 && p.Args[0] != "" {
+                       p.Path = p.Args[0]
+                       return
+               }
+               p.Path = p.Comm
+       }
+
+       pathLen := len(p.Path)
+       if pathLen >= 10 && p.Path[pathLen-10:] == " (deleted)" {
+               p.Path = p.Path[:len(p.Path)-10]
+       }
+
+       // We may receive relative paths from kernel, but the path of a process must be absolute
+       if core.IsAbsPath(p.Path) == false {
+               if err := p.ReadPath(); err != nil {
+                       log.Debug("ClenPath() error reading process path%s", err)
+                       return
+               }
+       }
+
+}
+
+// IsAlive checks if the process is still running
+func (p *Process) IsAlive() bool {
+       return core.Exists(core.ConcatStrings("/proc/", strconv.Itoa(p.ID)))
+}
diff --git a/daemon/procmon/ebpf/cache.go b/daemon/procmon/ebpf/cache.go
new file mode 100644 (file)
index 0000000..af0b78c
--- /dev/null
@@ -0,0 +1,195 @@
+package ebpf
+
+import (
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+)
+
+// NewExecEvent constructs a new execEvent from the arguments.
+func NewExecEvent(pid, ppid, uid uint32, path string, comm [16]byte) *execEvent {
+       ev := &execEvent{
+               Type: EV_TYPE_EXEC,
+               PID:  pid,
+               PPID: ppid,
+               UID:  uid,
+               Comm: comm,
+       }
+       length := MaxPathLen
+       if len(path) < MaxPathLen {
+               length = len(path)
+       }
+       copy(ev.Filename[:], path[:length])
+       return ev
+}
+
+type execEventItem struct {
+       Proc     procmon.Process
+       Event    execEvent
+       LastSeen int64
+}
+
+type eventsStore struct {
+       execEvents map[uint32]*execEventItem
+       sync.RWMutex
+}
+
+// NewEventsStore creates a new store of events.
+func NewEventsStore() *eventsStore {
+       return &eventsStore{
+               execEvents: make(map[uint32]*execEventItem),
+       }
+}
+
+func (e *eventsStore) add(key uint32, event execEvent, proc procmon.Process) {
+       e.Lock()
+       defer e.Unlock()
+       e.execEvents[key] = &execEventItem{
+               Proc:  proc,
+               Event: event,
+       }
+}
+
+func (e *eventsStore) isInStore(key uint32) (item *execEventItem, found bool) {
+       e.RLock()
+       defer e.RUnlock()
+       item, found = e.execEvents[key]
+       return
+}
+
+func (e *eventsStore) delete(key uint32) {
+       e.Lock()
+       defer e.Unlock()
+       delete(e.execEvents, key)
+}
+
+func (e *eventsStore) DeleteOldItems() {
+       e.Lock()
+       defer e.Unlock()
+
+       for k, item := range e.execEvents {
+               if item.Proc.IsAlive() == false {
+                       delete(e.execEvents, k)
+               }
+       }
+}
+
+//-----------------------------------------------------------------------------
+
+type ebpfCacheItem struct {
+       Key      []byte
+       Proc     procmon.Process
+       LastSeen int64
+}
+
+type ebpfCacheType struct {
+       Items map[interface{}]*ebpfCacheItem
+       sync.RWMutex
+}
+
+var (
+       maxTTL          = 40 // Seconds
+       maxCacheItems   = 5000
+       ebpfCache       *ebpfCacheType
+       ebpfCacheTicker *time.Ticker
+)
+
+// NewEbpfCacheItem creates a new cache item.
+func NewEbpfCacheItem(key []byte, proc procmon.Process) *ebpfCacheItem {
+       return &ebpfCacheItem{
+               Key:      key,
+               Proc:     proc,
+               LastSeen: time.Now().UnixNano(),
+       }
+}
+
+func (i *ebpfCacheItem) isValid() bool {
+       lastSeen := time.Now().Sub(
+               time.Unix(0, i.LastSeen),
+       )
+       return int(lastSeen.Seconds()) < maxTTL
+}
+
+// NewEbpfCache creates a new cache store.
+func NewEbpfCache() *ebpfCacheType {
+       ebpfCacheTicker = time.NewTicker(1 * time.Minute)
+       return &ebpfCacheType{
+               Items: make(map[interface{}]*ebpfCacheItem, 0),
+       }
+}
+
+func (e *ebpfCacheType) addNewItem(key interface{}, itemKey []byte, proc procmon.Process) {
+       e.Lock()
+       e.Items[key] = NewEbpfCacheItem(itemKey, proc)
+       e.Unlock()
+}
+
+func (e *ebpfCacheType) isInCache(key interface{}) (item *ebpfCacheItem, found bool) {
+       leng := e.Len()
+
+       e.Lock()
+       item, found = e.Items[key]
+       if found {
+               if item.isValid() {
+                       e.update(key, item)
+               } else {
+                       found = false
+                       delete(e.Items, key)
+               }
+       }
+       e.Unlock()
+
+       if leng > maxCacheItems {
+               e.DeleteOldItems()
+       }
+       return
+}
+
+func (e *ebpfCacheType) update(key interface{}, item *ebpfCacheItem) {
+       item.LastSeen = time.Now().UnixNano()
+       e.Items[key] = item
+}
+
+func (e *ebpfCacheType) Len() int {
+       e.RLock()
+       defer e.RUnlock()
+       return len(e.Items)
+}
+
+func (e *ebpfCacheType) DeleteOldItems() {
+       length := e.Len()
+
+       e.Lock()
+       defer e.Unlock()
+
+       for k, item := range e.Items {
+               if length > maxCacheItems || (item != nil && !item.isValid()) {
+                       delete(e.Items, k)
+               }
+       }
+}
+
+func (e *ebpfCacheType) delete(key interface{}) {
+       e.Lock()
+       defer e.Unlock()
+
+       if key, found := e.Items[key]; found {
+               delete(e.Items, key)
+       }
+}
+
+func (e *ebpfCacheType) clear() {
+       if e == nil {
+               return
+       }
+       e.Lock()
+       defer e.Unlock()
+       for k := range e.Items {
+               delete(e.Items, k)
+       }
+
+       if ebpfCacheTicker != nil {
+               ebpfCacheTicker.Stop()
+       }
+}
diff --git a/daemon/procmon/ebpf/debug.go b/daemon/procmon/ebpf/debug.go
new file mode 100644 (file)
index 0000000..60717c1
--- /dev/null
@@ -0,0 +1,101 @@
+package ebpf
+
+import (
+       "fmt"
+       "os/exec"
+       "strconv"
+       "syscall"
+       "unsafe"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
+       elf "github.com/iovisor/gobpf/elf"
+)
+
+// print map contents. used only for debugging
+func dumpMap(bpfmap *elf.Map, isIPv6 bool) {
+       var lookupKey []byte
+       var nextKey []byte
+       var value []byte
+       if !isIPv6 {
+               lookupKey = make([]byte, 12)
+               nextKey = make([]byte, 12)
+       } else {
+               lookupKey = make([]byte, 36)
+               nextKey = make([]byte, 36)
+       }
+       value = make([]byte, 40)
+       firstrun := true
+       i := 0
+       for {
+               i++
+               ok, err := m.LookupNextElement(bpfmap, unsafe.Pointer(&lookupKey[0]),
+                       unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value[0]))
+               if err != nil {
+                       log.Error("eBPF LookupNextElement error: %v", err)
+                       return
+               }
+               if firstrun {
+                       // on first run lookupKey is a dummy, nothing to delete
+                       firstrun = false
+                       copy(lookupKey, nextKey)
+                       continue
+               }
+               fmt.Println("key, value", lookupKey, value)
+
+               if !ok { //reached end of map
+                       break
+               }
+               copy(lookupKey, nextKey)
+       }
+}
+
+//PrintEverything prints all the stats. used only for debugging
+func PrintEverything() {
+       bash, _ := exec.LookPath("bash")
+       //get the number of the first map
+       out, err := exec.Command(bash, "-c", "bpftool map show | head -n 1 | cut -d ':' -f1").Output()
+       if err != nil {
+               fmt.Println("bpftool map dump name tcpMap ", err)
+       }
+       i, _ := strconv.Atoi(string(out[:len(out)-1]))
+       fmt.Println("i is", i)
+
+       //dump all maps for analysis
+       for j := i; j < i+14; j++ {
+               _, _ = exec.Command(bash, "-c", "bpftool map dump id "+strconv.Itoa(j)+" > dump"+strconv.Itoa(j)).Output()
+       }
+
+       alreadyEstablished.RLock()
+       for sock1, v := range alreadyEstablished.TCP {
+               fmt.Println(*sock1, v)
+       }
+
+       fmt.Println("---------------------")
+       for sock1, v := range alreadyEstablished.TCPv6 {
+               fmt.Println(*sock1, v)
+       }
+       alreadyEstablished.RUnlock()
+
+       fmt.Println("---------------------")
+       sockets, _ := daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_TCP)
+       for idx := range sockets {
+               fmt.Println("socket tcp: ", sockets[idx])
+       }
+       fmt.Println("---------------------")
+       sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_TCP)
+       for idx := range sockets {
+               fmt.Println("socket tcp6: ", sockets[idx])
+       }
+       fmt.Println("---------------------")
+       sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_UDP)
+       for idx := range sockets {
+               fmt.Println("socket udp: ", sockets[idx])
+       }
+       fmt.Println("---------------------")
+       sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_UDP)
+       for idx := range sockets {
+               fmt.Println("socket udp6: ", sockets[idx])
+       }
+
+}
diff --git a/daemon/procmon/ebpf/ebpf.go b/daemon/procmon/ebpf/ebpf.go
new file mode 100644 (file)
index 0000000..a3e6ded
--- /dev/null
@@ -0,0 +1,246 @@
+package ebpf
+
+import (
+       "context"
+       "encoding/binary"
+       "fmt"
+       "sync"
+       "syscall"
+       "unsafe"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       elf "github.com/iovisor/gobpf/elf"
+       "github.com/vishvananda/netlink"
+)
+
+// contains pointers to ebpf maps for a given protocol (tcp/udp/v6)
+type ebpfMapsForProto struct {
+       bpfmap *elf.Map
+}
+
+//Not in use, ~4usec faster lookup compared to m.LookupElement()
+
+// mimics union bpf_attr's anonymous struct used by BPF_MAP_*_ELEM commands
+// from <linux_headers>/include/uapi/linux/bpf.h
+type bpf_lookup_elem_t struct {
+       map_fd uint64 //even though in bpf.h its type is __u32, we must make it 8 bytes long
+       //because "key" is of type __aligned_u64, i.e. "key" must be aligned on an 8-byte boundary
+       key   uintptr
+       value uintptr
+}
+
+type alreadyEstablishedConns struct {
+       TCP   map[*daemonNetlink.Socket]int
+       TCPv6 map[*daemonNetlink.Socket]int
+       sync.RWMutex
+}
+
+// list of returned errors
+const (
+       NoError = iota
+       NotAvailable
+       EventsNotAvailable
+)
+
+// Error returns the error type and a message with the explanation
+type Error struct {
+       Msg  error
+       What int
+}
+
+var (
+       m, perfMod  *elf.Module
+       lock        = sync.RWMutex{}
+       mapSize     = uint(12000)
+       ebpfMaps    map[string]*ebpfMapsForProto
+       modulesPath string
+
+       //connections which were established at the time when opensnitch started
+       alreadyEstablished = alreadyEstablishedConns{
+               TCP:   make(map[*daemonNetlink.Socket]int),
+               TCPv6: make(map[*daemonNetlink.Socket]int),
+       }
+       ctxTasks    context.Context
+       cancelTasks context.CancelFunc
+       running     = false
+
+       maxKernelEvents = 32768
+       kernelEvents    = make(chan interface{}, maxKernelEvents)
+
+       // list of local addresses of this machine
+       localAddresses = make(map[string]netlink.Addr)
+
+       hostByteOrder binary.ByteOrder
+)
+
+// Start installs ebpf kprobes
+func Start(modPath string) *Error {
+       modulesPath = modPath
+
+       setRunning(false)
+       if err := mountDebugFS(); err != nil {
+               log.Error("ebpf.Start -> mount debugfs error. Report on github please: %s", err)
+               return &Error{
+                       fmt.Errorf("ebpf.Start: mount debugfs error. Report on github please: %s", err),
+                       NotAvailable,
+               }
+
+       }
+       var err error
+       m, err = core.LoadEbpfModule("opensnitch.o", modulesPath)
+       if err != nil {
+               log.Error("%s", err)
+               dispatchErrorEvent(fmt.Sprint("[eBPF]: ", err.Error()))
+               return &Error{
+                       fmt.Errorf("[eBPF] Error loading opensnitch.o: %s", err.Error()),
+                       NotAvailable,
+               }
+       }
+       m.EnableOptionCompatProbe()
+
+       // if previous shutdown was unclean, then we must remove the dangling kprobe
+       // and install it again (close the module and load it again)
+
+       if err := m.EnableKprobes(0); err != nil {
+               m.Close()
+               if err := m.Load(nil); err != nil {
+                       return &Error{
+                               fmt.Errorf("eBPF failed to load /etc/opensnitchd/opensnitch.o (2): %v", err),
+                               NotAvailable,
+                       }
+               }
+               if err := m.EnableKprobes(0); err != nil {
+                       return &Error{
+                               fmt.Errorf("eBPF error when enabling kprobes: %v", err),
+                               NotAvailable,
+                       }
+               }
+       }
+       determineHostByteOrder()
+
+       ebpfMaps = map[string]*ebpfMapsForProto{
+               "tcp": {
+                       bpfmap: m.Map("tcpMap")},
+               "tcp6": {
+                       bpfmap: m.Map("tcpv6Map")},
+               "udp": {
+                       bpfmap: m.Map("udpMap")},
+               "udp6": {
+                       bpfmap: m.Map("udpv6Map")},
+       }
+       for prot, mfp := range ebpfMaps {
+               if mfp.bpfmap == nil {
+                       return &Error{
+                               fmt.Errorf("eBPF module opensnitch.o malformed, bpfmap[%s] nil", prot),
+                               NotAvailable,
+                       }
+               }
+       }
+
+       ctxTasks, cancelTasks = context.WithCancel(context.Background())
+       ebpfCache = NewEbpfCache()
+       initEventsStreamer()
+
+       saveEstablishedConnections(uint8(syscall.AF_INET))
+       if core.IPv6Enabled {
+               saveEstablishedConnections(uint8(syscall.AF_INET6))
+       }
+
+       go monitorCache()
+       go monitorMaps()
+       go monitorLocalAddresses()
+       go monitorAlreadyEstablished()
+
+       setRunning(true)
+       return nil
+}
+
+func saveEstablishedConnections(commDomain uint8) error {
+       // save already established connections
+       socketListTCP, err := daemonNetlink.SocketsDump(commDomain, uint8(syscall.IPPROTO_TCP))
+       if err != nil {
+               log.Debug("eBPF could not dump TCP (%d) sockets via netlink: %v", commDomain, err)
+               return err
+       }
+
+       for _, sock := range socketListTCP {
+               inode := int((*sock).INode)
+               pid := procmon.GetPIDFromINode(inode, fmt.Sprint(inode,
+                       (*sock).ID.Source, (*sock).ID.SourcePort, (*sock).ID.Destination, (*sock).ID.DestinationPort))
+               alreadyEstablished.Lock()
+               alreadyEstablished.TCP[sock] = pid
+               alreadyEstablished.Unlock()
+       }
+       return nil
+}
+
+func setRunning(status bool) {
+       lock.Lock()
+       defer lock.Unlock()
+
+       running = status
+}
+
+// Stop stops monitoring connections using kprobes
+func Stop() {
+       lock.RLock()
+       defer lock.RUnlock()
+       if running == false {
+               return
+       }
+       cancelTasks()
+       ebpfCache.clear()
+
+       if m != nil {
+               m.Close()
+       }
+
+       for pm := range perfMapList {
+               if pm != nil {
+                       pm.PollStop()
+               }
+       }
+       for k, mod := range perfMapList {
+               if mod != nil {
+                       mod.Close()
+                       delete(perfMapList, k)
+               }
+       }
+       if perfMod != nil {
+               perfMod.Close()
+       }
+}
+
+// make bpf() syscall with bpf_lookup prepared by the caller
+func makeBpfSyscall(bpf_lookup *bpf_lookup_elem_t) uintptr {
+       BPF_MAP_LOOKUP_ELEM := 1 //cmd number
+       syscall_BPF := 321       //syscall number
+       sizeOfStruct := 40       //sizeof bpf_lookup_elem_t struct
+
+       r1, _, _ := syscall.Syscall(uintptr(syscall_BPF), uintptr(BPF_MAP_LOOKUP_ELEM),
+               uintptr(unsafe.Pointer(bpf_lookup)), uintptr(sizeOfStruct))
+       return r1
+}
+
+func dispatchErrorEvent(what string) {
+       log.Error(what)
+       dispatchEvent(what)
+}
+
+func dispatchEvent(data interface{}) {
+       if len(kernelEvents) > maxKernelEvents-1 {
+               fmt.Printf("kernelEvents queue full (%d)", len(kernelEvents))
+               <-kernelEvents
+       }
+       select {
+       case kernelEvents <- data:
+       default:
+       }
+}
+
+func Events() <-chan interface{} {
+       return kernelEvents
+}
diff --git a/daemon/procmon/ebpf/events.go b/daemon/procmon/ebpf/events.go
new file mode 100644 (file)
index 0000000..a76beba
--- /dev/null
@@ -0,0 +1,235 @@
+package ebpf
+
+import (
+       "bytes"
+       "encoding/binary"
+       "fmt"
+       "os"
+       "os/signal"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       elf "github.com/iovisor/gobpf/elf"
+)
+
+// MaxPathLen defines the maximum length of a path, as defined by the kernel:
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/limits.h#L13
+const MaxPathLen = 4096
+
+// MaxArgs defines the maximum number of arguments allowed
+const MaxArgs = 20
+
+// MaxArgLen defines the maximum length of each argument.
+// NOTE: this value is 131072 (PAGE_SIZE * 32)
+// https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/binfmts.h#L16
+const MaxArgLen = 256
+
+// TaskCommLen is the maximum num of characters of the comm field
+const TaskCommLen = 16
+
+type execEvent struct {
+       Type        uint64
+       PID         uint32
+       UID         uint32
+       PPID        uint32
+       RetCode     uint32
+       ArgsCount   uint8
+       ArgsPartial uint8
+       Filename    [MaxPathLen]byte
+       Args        [MaxArgs][MaxArgLen]byte
+       Comm        [TaskCommLen]byte
+       Pad1        uint16
+       Pad2        uint32
+}
+
+// Struct that holds the metadata of a connection.
+// When we receive a new connection, we look for it on the eBPF maps,
+// and if it's found, this information is returned.
+type networkEventT struct {
+       Pid  uint64
+       UID  uint64
+       Comm [TaskCommLen]byte
+}
+
+// List of supported events
+const (
+       EV_TYPE_NONE = iota
+       EV_TYPE_EXEC
+       EV_TYPE_EXECVEAT
+       EV_TYPE_FORK
+       EV_TYPE_SCHED_EXIT
+)
+
+var (
+       execEvents  = NewEventsStore()
+       perfMapList = make(map[*elf.PerfMap]*elf.Module)
+       // total workers spawned by the different events PerfMaps
+       eventWorkers = 0
+       perfMapName  = "proc-events"
+
+       // default value is 8.
+       // Not enough to handle high loads such http downloads, torent traffic, etc.
+       // (regular desktop usage)
+       ringBuffSize = 64 // * PAGE_SIZE (4k usually)
+)
+
+func initEventsStreamer() {
+       elfOpts := make(map[string]elf.SectionParams)
+       elfOpts["maps/"+perfMapName] = elf.SectionParams{PerfRingBufferPageCount: ringBuffSize}
+       var err error
+       perfMod, err = core.LoadEbpfModule("opensnitch-procs.o", modulesPath)
+       if err != nil {
+               dispatchErrorEvent(fmt.Sprint("[eBPF events]: ", err))
+               return
+       }
+       perfMod.EnableOptionCompatProbe()
+
+       if err = perfMod.Load(elfOpts); err != nil {
+               dispatchErrorEvent(fmt.Sprint("[eBPF events]: ", err))
+               return
+       }
+
+       tracepoints := []string{
+               "tracepoint/sched/sched_process_exit",
+               "tracepoint/syscalls/sys_enter_execve",
+               "tracepoint/syscalls/sys_enter_execveat",
+               "tracepoint/syscalls/sys_exit_execve",
+               "tracepoint/syscalls/sys_exit_execveat",
+               //"tracepoint/sched/sched_process_exec",
+               //"tracepoint/sched/sched_process_fork",
+       }
+
+       // Enable tracepoints first, that way if kprobes fail loading we'll still have some
+       for _, tp := range tracepoints {
+               err = perfMod.EnableTracepoint(tp)
+               if err != nil {
+                       dispatchErrorEvent(fmt.Sprintf("[eBPF events] error enabling tracepoint %s: %s", tp, err))
+               }
+       }
+
+       if err = perfMod.EnableKprobes(0); err != nil {
+               // if previous shutdown was unclean, then we must remove the dangling kprobe
+               // and install it again (close the module and load it again)
+               perfMod.Close()
+               if err = perfMod.Load(elfOpts); err != nil {
+                       dispatchErrorEvent(fmt.Sprintf("[eBPF events] failed to load /etc/opensnitchd/opensnitch-procs.o (2): %v", err))
+                       return
+               }
+               if err = perfMod.EnableKprobes(0); err != nil {
+                       dispatchErrorEvent(fmt.Sprintf("[eBPF events] error enabling kprobes: %v", err))
+               }
+       }
+
+       sig := make(chan os.Signal, 1)
+       signal.Notify(sig, os.Interrupt, os.Kill)
+       go func(sig chan os.Signal) {
+               <-sig
+       }(sig)
+
+       eventWorkers = 0
+       initPerfMap(perfMod)
+}
+
+func initPerfMap(mod *elf.Module) {
+       perfChan := make(chan []byte)
+       lostEvents := make(chan uint64, 1)
+       var err error
+       perfMap, err := elf.InitPerfMap(mod, perfMapName, perfChan, lostEvents)
+       if err != nil {
+               dispatchErrorEvent(fmt.Sprintf("[eBPF events] Error initializing eBPF events perfMap: %s", err))
+               return
+       }
+       perfMapList[perfMap] = mod
+
+       eventWorkers += 4
+       for i := 0; i < eventWorkers; i++ {
+               go streamEventsWorker(i, perfChan, lostEvents, kernelEvents, execEvents)
+       }
+       perfMap.PollStart()
+}
+
+func streamEventsWorker(id int, chn chan []byte, lost chan uint64, kernelEvents chan interface{}, execEvents *eventsStore) {
+       var event execEvent
+       errors := 0
+       maxErrors := 20 // we should have no errors.
+       tooManyErrors := func() bool {
+               errors++
+               if errors > maxErrors {
+                       log.Error("[eBPF events] too many errors parsing events from kernel")
+                       log.Error("verify that you're using the correct eBPF modules for this version (%s)", core.Version)
+                       return true
+               }
+               return false
+       }
+
+       for {
+               select {
+               case <-ctxTasks.Done():
+                       goto Exit
+               case l := <-lost:
+                       log.Debug("Lost ebpf events: %d", l)
+               case d := <-chn:
+                       if err := binary.Read(bytes.NewBuffer(d), hostByteOrder, &event); err != nil {
+                               log.Debug("[eBPF events #%d] error: %s", id, err)
+                               if tooManyErrors() {
+                                       goto Exit
+                               }
+
+                       } else {
+                               switch event.Type {
+                               case EV_TYPE_EXEC, EV_TYPE_EXECVEAT:
+                                       if _, found := execEvents.isInStore(event.PID); found {
+                                               log.Debug("[eBPF event inCache] -> %d", event.PID)
+                                               continue
+                                       }
+                                       proc := event2process(&event)
+                                       if proc == nil {
+                                               continue
+                                       }
+                                       execEvents.add(event.PID, event, *proc)
+
+                               case EV_TYPE_SCHED_EXIT:
+                                       log.Debug("[eBPF exit event] -> %d", event.PID)
+                                       if _, found := execEvents.isInStore(event.PID); found {
+                                               log.Debug("[eBPF exit event inCache] -> %d", event.PID)
+                                               execEvents.delete(event.PID)
+                                       }
+                               }
+                       }
+               }
+       }
+
+Exit:
+       log.Debug("perfMap goroutine exited #%d", id)
+}
+
+func event2process(event *execEvent) (proc *procmon.Process) {
+
+       proc = procmon.NewProcess(int(event.PID), byteArrayToString(event.Comm[:]))
+       // trust process path received from kernel
+       path := byteArrayToString(event.Filename[:])
+       if path != "" {
+               proc.SetPath(path)
+       } else {
+               if proc.ReadPath() != nil {
+                       return nil
+               }
+       }
+       proc.ReadCwd()
+       proc.ReadEnv()
+       proc.UID = int(event.UID)
+       proc.PPID = int(event.PPID)
+
+       if event.ArgsPartial == 0 {
+               for i := 0; i < int(event.ArgsCount); i++ {
+                       proc.Args = append(proc.Args, byteArrayToString(event.Args[i][:]))
+               }
+               proc.CleanArgs()
+       } else {
+               proc.ReadCmdline()
+       }
+       log.Debug("[eBPF exec event] ppid: %d, pid: %d, %s -> %s", event.PPID, event.PID, proc.Path, proc.Args)
+
+       return
+}
diff --git a/daemon/procmon/ebpf/find.go b/daemon/procmon/ebpf/find.go
new file mode 100644 (file)
index 0000000..1c53646
--- /dev/null
@@ -0,0 +1,232 @@
+package ebpf
+
+import (
+       "encoding/binary"
+       "fmt"
+       "net"
+       "strconv"
+       "unsafe"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+)
+
+// we need to manually remove old connections from a bpf map
+
+// GetPid looks up process pid in a bpf map.
+// If it's not found, it searches already-established TCP connections.
+// Returns the process if found.
+// Additionally, if the process has been found by swapping fields, it'll return
+// a flag indicating it.
+func GetPid(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (*procmon.Process, bool, error) {
+       if proc := getPidFromEbpf(proto, srcPort, srcIP, dstIP, dstPort); proc != nil {
+               return proc, false, nil
+       }
+       if findAddressInLocalAddresses(dstIP) {
+               // FIXME: systemd-resolved sometimes makes a TCP Fast Open connection to a DNS server (8.8.8.8 on my machine)
+               // and we get a packet here with **source** (not detination!!!) IP 8.8.8.8
+               // Maybe it's an in-kernel response with spoofed IP because resolved's TCP Fast Open packet, nor the response.
+               // Another scenario when systemd-resolved or dnscrypt-proxy is used, is that every outbound connection has
+               // the fields swapped:
+               // 443:public-ip -> local-ip:local-port , like if it was a response (but it's not).
+               // Swapping connection fields helps to identify the connection + pid + process, and continue working as usual
+               // when systemd-resolved is being used. But we should understand why is this happenning.
+
+               if proc := getPidFromEbpf(proto, dstPort, dstIP, srcIP, srcPort); proc != nil {
+                       return proc, true, fmt.Errorf("[ebpf conn] FIXME: found swapping fields, systemd-resolved is that you? set DNS=x.x.x.x to your DNS server in /etc/systemd/resolved.conf to workaround this problem")
+               }
+               return nil, false, fmt.Errorf("[ebpf conn] unknown source IP: %s", srcIP)
+       }
+       //check if it comes from already established TCP
+       if proto == "tcp" || proto == "tcp6" {
+               if pid, uid, err := findInAlreadyEstablishedTCP(proto, srcPort, srcIP, dstIP, dstPort); err == nil {
+                       proc := procmon.NewProcess(pid, "")
+                       proc.GetInfo()
+                       proc.UID = uid
+                       return proc, false, nil
+               }
+       }
+
+       //using netlink.GetSocketInfo to check if UID is 0 (in-kernel connection)
+       if uid, _ := daemonNetlink.GetSocketInfo(proto, srcIP, srcPort, dstIP, dstPort); uid == 0 {
+               return nil, false, nil
+       }
+       return nil, false, nil
+}
+
+// getPidFromEbpf looks up a connection in bpf map and returns PID if found
+// the lookup keys and values are defined in opensnitch.c , e.g.
+//
+// struct tcp_key_t {
+//     u16 sport;
+//     u32 daddr;
+//     u16 dport;
+//  u32 saddr;
+// }__attribute__((packed));
+
+// struct tcp_value_t{
+//     u64 pid;
+//  u64 uid;
+//     u64 counter;
+//  char[TASK_COMM_LEN] comm; // 16 bytes
+// }__attribute__((packed));
+
+func getPidFromEbpf(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (proc *procmon.Process) {
+       // Some connections, like broadcasts, are only seen in eBPF once,
+       // but some applications send 1 connection per network interface.
+       // If we delete the eBPF entry the first time we see it, we won't find
+       // the connection the next times.
+       delItemIfFound := true
+
+       _, ok := ebpfMaps[proto]
+       if !ok {
+               return
+       }
+
+       var value networkEventT
+       var key []byte
+       var isIP4 bool = (proto == "tcp") || (proto == "udp") || (proto == "udplite")
+
+       if isIP4 {
+               key = make([]byte, 12)
+               copy(key[2:6], dstIP)
+               binary.BigEndian.PutUint16(key[6:8], uint16(dstPort))
+               copy(key[8:12], srcIP)
+       } else { // IPv6
+               key = make([]byte, 36)
+               copy(key[2:18], dstIP)
+               binary.BigEndian.PutUint16(key[18:20], uint16(dstPort))
+               copy(key[20:36], srcIP)
+       }
+       hostByteOrder.PutUint16(key[0:2], uint16(srcPort))
+
+       k := core.ConcatStrings(
+               proto,
+               strconv.FormatUint(uint64(srcPort), 10),
+               srcIP.String(),
+               dstIP.String(),
+               strconv.FormatUint(uint64(dstPort), 10))
+       if cacheItem, isInCache := ebpfCache.isInCache(k); isInCache {
+               // should we re-read the info?
+               // environ vars might have changed
+               //proc.GetInfo()
+               deleteEbpfEntry(proto, unsafe.Pointer(&key[0]))
+               proc = &cacheItem.Proc
+               log.Debug("[ebpf conn] in cache: %s, %d -> %s", k, proc.ID, proc.Path)
+               return
+       }
+
+       err := m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value))
+       if err != nil {
+               // key not found
+               // sometimes srcIP is 0.0.0.0. Happens especially with UDP sendto()
+               // for example: 57621:10.0.3.1 -> 10.0.3.255:57621 , reported as: 0.0.0.0 -> 10.0.3.255
+               if isIP4 {
+                       zeroes := make([]byte, 4)
+                       copy(key[8:12], zeroes)
+               } else {
+                       zeroes := make([]byte, 16)
+                       copy(key[20:36], zeroes)
+               }
+               err = m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value))
+               if err == nil {
+                       delItemIfFound = false
+               }
+       }
+       if err != nil && proto == "udp" && srcIP.String() == dstIP.String() {
+               // very rarely I see this connection. It has srcIP and dstIP == 0.0.0.0 in ebpf map
+               // it is a localhost to localhost connection
+               // srcIP was already set to 0, set dstIP to zero also
+               // TODO try to reproduce it and look for srcIP/dstIP in other kernel structures
+               zeroes := make([]byte, 4)
+               copy(key[2:6], zeroes)
+               err = m.LookupElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&key[0]), unsafe.Pointer(&value))
+       }
+
+       if err != nil {
+               // key not found in bpf maps
+               return nil
+       }
+
+       proc = findConnProcess(&value, k)
+
+       log.Debug("[ebpf conn] adding item to cache: %s", k)
+       ebpfCache.addNewItem(k, key, *proc)
+       if delItemIfFound {
+               deleteEbpfEntry(proto, unsafe.Pointer(&key[0]))
+       }
+       return
+}
+
+// findConnProcess finds the process' details of a connection.
+// By default we only receive the PID of the process, so we need to get
+// the rest of the details.
+// TODO: get the details from kernel, with mm_struct (exe_file, fd_path, etc).
+func findConnProcess(value *networkEventT, connKey string) (proc *procmon.Process) {
+       comm := byteArrayToString(value.Comm[:])
+       proc = procmon.NewProcess(int(value.Pid), comm)
+       // Use socket's UID. A process may have dropped privileges.
+       // This is the UID that we've always used.
+       proc.UID = int(value.UID)
+
+       err := proc.ReadPath()
+       if ev, found := execEvents.isInStore(uint32(value.Pid)); found {
+               // use socket's UID. See above why ^
+               ev.Proc.UID = proc.UID
+               ev.Proc.ReadCmdline()
+               // if proc's ReadPath() has been successfull, and the path received via the execve tracepoint differs,
+               // use proc's path.
+               // Sometimes we received from the tracepoint a wrong/non-existent path.
+               // Othertimes we receive a "helper" that executes the real binary which opens the connection.
+               // Downsides: for execveat() executions we won't display the original binary.
+               if err == nil && ev.Proc.Path != proc.Path {
+                       proc.ReadCmdline()
+                       ev.Proc.Path = proc.Path
+                       ev.Proc.Args = proc.Args
+               }
+               proc = &ev.Proc
+
+               log.Debug("[ebpf conn] not in cache, but in execEvents: %s, %d -> %s", connKey, proc.ID, proc.Path)
+       } else {
+               log.Debug("[ebpf conn] not in cache, NOR in execEvents: %s, %d -> %s", connKey, proc.ID, proc.Path)
+               // We'll end here if the events module has not been loaded, or if the process is not in cache.
+               proc.GetInfo()
+               execEvents.add(uint32(value.Pid),
+                       *NewExecEvent(uint32(value.Pid), 0, uint32(value.UID), proc.Path, value.Comm),
+                       *proc)
+       }
+
+       return
+}
+
+// FindInAlreadyEstablishedTCP searches those TCP connections which were already established at the time
+// when opensnitch started
+func findInAlreadyEstablishedTCP(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint) (int, int, error) {
+       alreadyEstablished.RLock()
+       defer alreadyEstablished.RUnlock()
+
+       var _alreadyEstablished map[*daemonNetlink.Socket]int
+       if proto == "tcp" {
+               _alreadyEstablished = alreadyEstablished.TCP
+       } else if proto == "tcp6" {
+               _alreadyEstablished = alreadyEstablished.TCPv6
+       }
+
+       for sock, v := range _alreadyEstablished {
+               if (*sock).ID.SourcePort == uint16(srcPort) && (*sock).ID.Source.Equal(srcIP) &&
+                       (*sock).ID.Destination.Equal(dstIP) && (*sock).ID.DestinationPort == uint16(dstPort) {
+                       return v, int((*sock).UID), nil
+               }
+       }
+       return -1, -1, fmt.Errorf("eBPF inode not found")
+}
+
+//returns true if addr is in the list of this machine's addresses
+func findAddressInLocalAddresses(addr net.IP) bool {
+       lock.Lock()
+       defer lock.Unlock()
+       _, found := localAddresses[addr.String()]
+       return found
+}
diff --git a/daemon/procmon/ebpf/monitor.go b/daemon/procmon/ebpf/monitor.go
new file mode 100644 (file)
index 0000000..2f15483
--- /dev/null
@@ -0,0 +1,163 @@
+package ebpf
+
+import (
+       "syscall"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink"
+       "github.com/vishvananda/netlink"
+)
+
+// we need to manually remove old connections from a bpf map
+// since when a bpf map is full it doesn't allow any more insertions
+func monitorMaps() {
+       for {
+               select {
+               case <-ctxTasks.Done():
+                       goto Exit
+               default:
+                       time.Sleep(time.Second * 5)
+                       for name := range ebpfMaps {
+                               // using a pointer to the map doesn't delete the items.
+                               // bpftool still counts them.
+                               if items := getItems(name, name == "tcp6" || name == "udp6"); items > 500 {
+                                       deleted := deleteOldItems(name, name == "tcp6" || name == "udp6", items/2)
+                                       log.Debug("[ebpf] old items deleted: %d", deleted)
+                               }
+                       }
+               }
+       }
+Exit:
+}
+
+func monitorCache() {
+       for {
+               select {
+               case <-ctxTasks.Done():
+                       goto Exit
+               case <-ebpfCacheTicker.C:
+                       ebpfCache.DeleteOldItems()
+                       execEvents.DeleteOldItems()
+               }
+       }
+Exit:
+}
+
+// maintain a list of this machine's local addresses
+func monitorLocalAddresses() {
+       newAddrChan := make(chan netlink.AddrUpdate)
+       done := make(chan struct{})
+       defer close(done)
+
+       lock.Lock()
+       localAddresses = daemonNetlink.GetLocalAddrs()
+       lock.Unlock()
+
+       netlink.AddrSubscribeWithOptions(newAddrChan, done,
+               netlink.AddrSubscribeOptions{
+                       ErrorCallback: func(err error) {
+                               log.Error("AddrSubscribeWithOptions error: %s", err)
+                       },
+                       ListExisting: true,
+               })
+
+       for {
+               select {
+               case <-ctxTasks.Done():
+                       done <- struct{}{}
+                       goto Exit
+               case addr := <-newAddrChan:
+                       if addr.NewAddr && !findAddressInLocalAddresses(addr.LinkAddress.IP) {
+                               log.Debug("local addr added: %+v\n", addr)
+                               lock.Lock()
+
+                               localAddresses[addr.LinkAddress.IP.String()] = daemonNetlink.AddrUpdateToAddr(&addr)
+
+                               lock.Unlock()
+                       } else if !addr.NewAddr {
+                               log.Debug("local addr removed: %+v\n", addr)
+                               lock.Lock()
+                               delete(localAddresses, addr.LinkAddress.IP.String())
+                               lock.Unlock()
+                       }
+               }
+       }
+Exit:
+       log.Debug("monitorLocalAddresses exited")
+}
+
+// monitorAlreadyEstablished makes sure that when an already-established connection is closed
+// it will be removed from alreadyEstablished. If we don't do this and keep the alreadyEstablished entry forever,
+// then after the genuine process quits,a malicious process may reuse PID-srcPort-srcIP-dstPort-dstIP
+func monitorAlreadyEstablished() {
+       tcperr := 0
+       errLimitExceeded := func() bool {
+               if tcperr > 100 {
+                       log.Debug("monitorAlreadyEstablished() generated too much errors")
+                       return true
+               }
+               tcperr++
+
+               return false
+       }
+
+       for {
+               select {
+               case <-ctxTasks.Done():
+                       goto Exit
+               default:
+                       time.Sleep(time.Second * 2)
+                       socketListTCP, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET), uint8(syscall.IPPROTO_TCP))
+                       if err != nil {
+                               log.Debug("monitorAlreadyEstablished(), error dumping TCP sockets via netlink (%d): %s", tcperr, err)
+                               if errLimitExceeded() {
+                                       goto Exit
+                               }
+                               continue
+                       }
+                       alreadyEstablished.Lock()
+                       for aesock := range alreadyEstablished.TCP {
+                               found := false
+                               for _, sock := range socketListTCP {
+                                       if daemonNetlink.SocketsAreEqual(aesock, sock) {
+                                               found = true
+                                               break
+                                       }
+                               }
+                               if !found {
+                                       delete(alreadyEstablished.TCP, aesock)
+                               }
+                       }
+                       alreadyEstablished.Unlock()
+
+                       if core.IPv6Enabled {
+                               socketListTCPv6, err := daemonNetlink.SocketsDump(uint8(syscall.AF_INET6), uint8(syscall.IPPROTO_TCP))
+                               if err != nil {
+                                       if errLimitExceeded() {
+                                               goto Exit
+                                       }
+                                       log.Debug("monitorAlreadyEstablished(), error dumping TCPv6 sockets via netlink (%d): %s", tcperr, err)
+                                       continue
+                               }
+                               alreadyEstablished.Lock()
+                               for aesock := range alreadyEstablished.TCPv6 {
+                                       found := false
+                                       for _, sock := range socketListTCPv6 {
+                                               if daemonNetlink.SocketsAreEqual(aesock, sock) {
+                                                       found = true
+                                                       break
+                                               }
+                                       }
+                                       if !found {
+                                               delete(alreadyEstablished.TCPv6, aesock)
+                                       }
+                               }
+                               alreadyEstablished.Unlock()
+                       }
+               }
+       }
+Exit:
+       log.Debug("monitorAlreadyEstablished exited")
+}
diff --git a/daemon/procmon/ebpf/utils.go b/daemon/procmon/ebpf/utils.go
new file mode 100644 (file)
index 0000000..874e858
--- /dev/null
@@ -0,0 +1,164 @@
+package ebpf
+
+import (
+       "bytes"
+       "encoding/binary"
+       "fmt"
+       "unsafe"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+func determineHostByteOrder() {
+       lock.Lock()
+       //determine host byte order
+       buf := [2]byte{}
+       *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xABCD)
+       switch buf {
+       case [2]byte{0xCD, 0xAB}:
+               hostByteOrder = binary.LittleEndian
+       case [2]byte{0xAB, 0xCD}:
+               hostByteOrder = binary.BigEndian
+       default:
+               log.Error("Could not determine host byte order.")
+       }
+       lock.Unlock()
+}
+
+func mountDebugFS() error {
+       debugfsPath := "/sys/kernel/debug/"
+       kprobesPath := fmt.Sprint(debugfsPath, "tracing/kprobe_events")
+       if core.Exists(kprobesPath) == false {
+               if _, err := core.Exec("mount", []string{"-t", "debugfs", "none", debugfsPath}); err != nil {
+                       log.Warning("eBPF debugfs error: %s", err)
+                       return fmt.Errorf(`%s
+Unable to access debugfs filesystem, needed for eBPF to work, likely caused by a hardened or customized kernel.
+Change process monitor method to 'proc' to stop receiving this alert
+                       `, err)
+               }
+       }
+
+       return nil
+}
+
+// Trim null characters, and return the left part of the byte array.
+// NOTE: using BPF_MAP_TYPE_PERCPU_ARRAY does not initialize strings to 0,
+// so we end up receiving events as follow:
+// event.filename -> /usr/bin/iptables
+// event.filename -> /bin/lsn/iptables (should be /bin/ls)
+// It turns out, that there's a 0x00 character between "/bin/ls" and "n/iptables":
+// [47 115 98 105 110 47 100 117 109 112 101 50 102 115 0 0 101 115
+//                                                      ^^^
+// TODO: investigate if there's any way of initializing the struct to 0
+// like using __builtin_memset() (can't be used with PERCPU apparently)
+func byteArrayToString(arr []byte) string {
+       temp := bytes.SplitAfter(arr, []byte("\x00"))[0]
+       return string(bytes.Trim(temp[:], "\x00"))
+}
+
+func deleteEbpfEntry(proto string, key unsafe.Pointer) bool {
+       if err := m.DeleteElement(ebpfMaps[proto].bpfmap, key); err != nil {
+               log.Debug("error deleting ebpf entry: %s", err)
+               return false
+       }
+       return true
+}
+
+func getItems(proto string, isIPv6 bool) (items uint) {
+       isDup := make(map[string]uint8)
+       var lookupKey []byte
+       var nextKey []byte
+
+       if !isIPv6 {
+               lookupKey = make([]byte, 12)
+               nextKey = make([]byte, 12)
+       } else {
+               lookupKey = make([]byte, 36)
+               nextKey = make([]byte, 36)
+       }
+       var value networkEventT
+       firstrun := true
+
+       for {
+               mp, ok := ebpfMaps[proto]
+               if !ok {
+                       return
+               }
+               ok, err := m.LookupNextElement(mp.bpfmap, unsafe.Pointer(&lookupKey[0]),
+                       unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value))
+               if !ok || err != nil { //reached end of map
+                       log.Debug("[ebpf] %s map: %d active items", proto, items)
+                       return
+               }
+               if firstrun {
+                       // on first run lookupKey is a dummy, nothing to delete
+                       firstrun = false
+                       copy(lookupKey, nextKey)
+                       continue
+               }
+               if counter, duped := isDup[string(lookupKey)]; duped && counter > 1 {
+                       deleteEbpfEntry(proto, unsafe.Pointer(&lookupKey[0]))
+                       continue
+               }
+               isDup[string(lookupKey)]++
+               copy(lookupKey, nextKey)
+               items++
+       }
+
+       return items
+}
+
+// deleteOldItems deletes maps' elements in order to keep them below maximum capacity.
+// If ebpf maps are full they don't allow any more insertions, ending up lossing events.
+func deleteOldItems(proto string, isIPv6 bool, maxToDelete uint) (deleted uint) {
+       isDup := make(map[string]uint8)
+       var lookupKey []byte
+       var nextKey []byte
+       if !isIPv6 {
+               lookupKey = make([]byte, 12)
+               nextKey = make([]byte, 12)
+       } else {
+               lookupKey = make([]byte, 36)
+               nextKey = make([]byte, 36)
+       }
+       var value networkEventT
+       firstrun := true
+       i := uint(0)
+
+       for {
+               i++
+               if i > maxToDelete {
+                       return
+               }
+               ok, err := m.LookupNextElement(ebpfMaps[proto].bpfmap, unsafe.Pointer(&lookupKey[0]),
+                       unsafe.Pointer(&nextKey[0]), unsafe.Pointer(&value))
+               if !ok || err != nil { //reached end of map
+                       return
+               }
+               if _, duped := isDup[string(lookupKey)]; duped {
+                       if deleteEbpfEntry(proto, unsafe.Pointer(&lookupKey[0])) {
+                               deleted++
+                               copy(lookupKey, nextKey)
+                               continue
+                       }
+                       return
+               }
+
+               if firstrun {
+                       // on first run lookupKey is a dummy, nothing to delete
+                       firstrun = false
+                       copy(lookupKey, nextKey)
+                       continue
+               }
+
+               if !deleteEbpfEntry(proto, unsafe.Pointer(&lookupKey[0])) {
+                       return
+               }
+               deleted++
+               isDup[string(lookupKey)]++
+               copy(lookupKey, nextKey)
+       }
+
+       return
+}
diff --git a/daemon/procmon/find.go b/daemon/procmon/find.go
new file mode 100644 (file)
index 0000000..3bf4581
--- /dev/null
@@ -0,0 +1,108 @@
+package procmon
+
+import (
+       "os"
+       "sort"
+       "strconv"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+)
+
+func sortPidsByTime(fdList []os.FileInfo) []os.FileInfo {
+       sort.Slice(fdList, func(i, j int) bool {
+               t := fdList[i].ModTime().UnixNano()
+               u := fdList[j].ModTime().UnixNano()
+               return t > u
+       })
+       return fdList
+}
+
+// inodeFound searches for the given inode in /proc/<pid>/fd/ or
+// /proc/<pid>/task/<tid>/fd/ and gets the symbolink link it points to,
+// in order to compare it against the given inode.
+// If the inode is found, the cache is updated ans sorted.
+func inodeFound(pidsPath, expect, inodeKey string, inode, pid int) bool {
+       fdPath := core.ConcatStrings(pidsPath, strconv.Itoa(pid), "/fd/")
+       fdList := lookupPidDescriptors(fdPath, pid)
+       if fdList == nil {
+               return false
+       }
+
+       for idx := 0; idx < len(fdList); idx++ {
+               descLink := core.ConcatStrings(fdPath, fdList[idx])
+               if link, err := os.Readlink(descLink); err == nil && link == expect {
+                       inodesCache.add(inodeKey, descLink, pid)
+                       pidsCache.add(fdPath, fdList, pid)
+                       return true
+               }
+       }
+
+       return false
+}
+
+// lookupPidInProc searches for an inode in /proc.
+// First it gets the running PIDs and obtains the opened sockets.
+// TODO: If the inode is not found, search again in the task/threads
+// of every PID (costly).
+func lookupPidInProc(pidsPath, expect, inodeKey string, inode int) int {
+       pidList := getProcPids(pidsPath)
+       for _, pid := range pidList {
+               if inodeFound(pidsPath, expect, inodeKey, inode, pid) {
+                       return pid
+               }
+       }
+       return -1
+}
+
+// lookupPidDescriptors returns the list of descriptors inside
+// /proc/<pid>/fd/
+// TODO: search in /proc/<pid>/task/<tid>/fd/ .
+func lookupPidDescriptors(fdPath string, pid int) []string {
+       f, err := os.Open(fdPath)
+       if err != nil {
+               return nil
+       }
+       // This is where most of the time is wasted when looking for PIDs.
+       // long running processes like firefox/chrome tend to have a lot of descriptor
+       // references that points to non existent files on disk, but that remains in
+       // memory (those with " (deleted)").
+       // This causes to have to iterate over 300 to 700 items, that are not sockets.
+       fdList, err := f.Readdir(-1)
+       f.Close()
+       if err != nil {
+               return nil
+       }
+       fdList = sortPidsByTime(fdList)
+
+       s := make([]string, len(fdList))
+       for n, f := range fdList {
+               s[n] = f.Name()
+       }
+
+       return s
+}
+
+// getProcPids returns the list of running PIDs, /proc or /proc/<pid>/task/ .
+func getProcPids(pidsPath string) (pidList []int) {
+       f, err := os.Open(pidsPath)
+       if err != nil {
+               return pidList
+       }
+       ls, err := f.Readdir(-1)
+       f.Close()
+       if err != nil {
+               return pidList
+       }
+       ls = sortPidsByTime(ls)
+
+       for _, f := range ls {
+               if f.IsDir() == false {
+                       continue
+               }
+               if pid, err := strconv.Atoi(f.Name()); err == nil {
+                       pidList = append(pidList, []int{pid}...)
+               }
+       }
+
+       return pidList
+}
diff --git a/daemon/procmon/find_test.go b/daemon/procmon/find_test.go
new file mode 100644 (file)
index 0000000..9588de8
--- /dev/null
@@ -0,0 +1,42 @@
+package procmon
+
+import (
+       "fmt"
+       "testing"
+)
+
+func TestGetProcPids(t *testing.T) {
+       pids := getProcPids("/proc")
+
+       if len(pids) == 0 {
+               t.Error("getProcPids() should not be 0", pids)
+       }
+}
+
+func TestLookupPidDescriptors(t *testing.T) {
+       pidsFd := lookupPidDescriptors(fmt.Sprint("/proc/", myPid, "/fd/"), myPid)
+       if len(pidsFd) == 0 {
+               t.Error("getProcPids() should not be 0", pidsFd)
+       }
+}
+
+func TestLookupPidInProc(t *testing.T) {
+       // we expect that the inode 1 points to /dev/null
+       expect := "/dev/null"
+       foundPid := lookupPidInProc("/proc/", expect, "", myPid)
+       if foundPid == -1 {
+               t.Error("lookupPidInProc() should not return -1")
+       }
+}
+
+func BenchmarkGetProcs(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               getProcPids("/proc")
+       }
+}
+
+func BenchmarkLookupPidDescriptors(b *testing.B) {
+       for i := 0; i < b.N; i++ {
+               lookupPidDescriptors(fmt.Sprint("/proc/", myPid, "/fd/"), myPid)
+       }
+}
diff --git a/daemon/procmon/monitor/init.go b/daemon/procmon/monitor/init.go
new file mode 100644 (file)
index 0000000..85dfcbd
--- /dev/null
@@ -0,0 +1,112 @@
+package monitor
+
+import (
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       "github.com/evilsocket/opensnitch/daemon/procmon/audit"
+       "github.com/evilsocket/opensnitch/daemon/procmon/ebpf"
+)
+
+var (
+       cacheMonitorsRunning = false
+)
+
+// List of errors that this package may return.
+const (
+       NoError = iota
+       ProcFsErr
+       AuditdErr
+       EbpfErr
+       EbpfEventsErr
+)
+
+// Error wraps the type of error with its message
+type Error struct {
+       What int
+       Msg  error
+}
+
+// ReconfigureMonitorMethod configures a new method for parsing connections.
+func ReconfigureMonitorMethod(newMonitorMethod, ebpfModulesPath string) *Error {
+       if procmon.GetMonitorMethod() == newMonitorMethod {
+               return nil
+       }
+
+       oldMethod := procmon.GetMonitorMethod()
+       if oldMethod == "" {
+               oldMethod = procmon.MethodProc
+       }
+       End()
+       procmon.SetMonitorMethod(newMonitorMethod)
+       // if the new monitor method fails to start, rollback the change and exit
+       // without saving the configuration. Otherwise we can end up with the wrong
+       // monitor method configured and saved to file.
+       err := Init(ebpfModulesPath)
+       if err.What > NoError {
+               log.Error("Reconf() -> Init() error: %v", err)
+               procmon.SetMonitorMethod(oldMethod)
+               return err
+       }
+
+       return nil
+}
+
+// End stops the way of parsing new connections.
+func End() {
+       if procmon.MethodIsAudit() {
+               audit.Stop()
+       } else if procmon.MethodIsEbpf() {
+               ebpf.Stop()
+       }
+}
+
+// Init starts parsing connections using the method specified.
+func Init(ebpfModulesPath string) (errm *Error) {
+       errm = &Error{}
+
+       if cacheMonitorsRunning == false {
+               go procmon.MonitorActivePids()
+               go procmon.CacheCleanerTask()
+               cacheMonitorsRunning = true
+       }
+
+       if procmon.MethodIsEbpf() {
+               err := ebpf.Start(ebpfModulesPath)
+               if err == nil {
+                       log.Info("Process monitor method ebpf")
+                       return errm
+               }
+               // ebpf main module loaded, we can use ebpf
+
+               // XXX: this will have to be rewritten when we'll have more events (bind, listen, etc)
+               if err.What == ebpf.EventsNotAvailable {
+                       log.Info("Process monitor method ebpf")
+                       log.Warning("opensnitch-procs.o not available: %s", err.Msg)
+
+                       return errm
+               }
+
+               // we need to stop this method even if it has failed to start, in order to clean up the kprobes
+               // It helps with the error "cannot write...kprobe_events: file exists".
+               ebpf.Stop()
+               errm.What = err.What
+               errm.Msg = err.Msg
+               log.Warning("error starting ebpf monitor method: %v", err)
+
+       } else if procmon.MethodIsAudit() {
+               auditConn, err := audit.Start()
+               if err == nil {
+                       log.Info("Process monitor method audit")
+                       go audit.Reader(auditConn, (chan<- audit.Event)(audit.EventChan))
+                       return &Error{AuditdErr, err}
+               }
+               errm.What = AuditdErr
+               errm.Msg = err
+               log.Warning("error starting audit monitor method: %v", err)
+       }
+
+       // if any of the above methods have failed, fallback to proc
+       log.Info("Process monitor method /proc")
+       procmon.SetMonitorMethod(procmon.MethodProc)
+       return errm
+}
diff --git a/daemon/procmon/parse.go b/daemon/procmon/parse.go
new file mode 100644 (file)
index 0000000..224ff16
--- /dev/null
@@ -0,0 +1,112 @@
+package procmon
+
+import (
+       "fmt"
+       "net"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/netstat"
+       "github.com/evilsocket/opensnitch/daemon/procmon/audit"
+)
+
+func getPIDFromAuditEvents(inode int, inodeKey string, expect string) (int, int) {
+       audit.Lock.RLock()
+       defer audit.Lock.RUnlock()
+
+       auditEvents := audit.GetEvents()
+       for n := 0; n < len(auditEvents); n++ {
+               pid := auditEvents[n].Pid
+               if inodeFound("/proc/", expect, inodeKey, inode, pid) {
+                       return pid, n
+               }
+       }
+       for n := 0; n < len(auditEvents); n++ {
+               ppid := auditEvents[n].PPid
+               if inodeFound("/proc/", expect, inodeKey, inode, ppid) {
+                       return ppid, n
+               }
+       }
+       return -1, -1
+}
+
+// GetInodeFromNetstat tries to obtain the inode of a connection from /proc/net/*
+func GetInodeFromNetstat(netEntry *netstat.Entry, inodeList *[]int, protocol string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) bool {
+       if netEntry = netstat.FindEntry(protocol, srcIP, srcPort, dstIP, dstPort); netEntry == nil {
+               log.Debug("Could not find netstat entry for: (%s) %d:%s -> %s:%d", protocol, srcPort, srcIP, dstIP, dstPort)
+               return false
+       }
+       if netEntry.INode > 0 {
+               log.Debug("connection found in netstat: %#v", netEntry)
+               *inodeList = append([]int{netEntry.INode}, *inodeList...)
+               return true
+       }
+       log.Debug("<== no inodes found for this connection: %#v", netEntry)
+
+       return false
+}
+
+// GetPIDFromINode tries to get the PID from a socket inode following these steps:
+// 1. Get the PID from the cache of Inodes.
+// 2. Get the PID from the cache of PIDs.
+// 3. Look for the PID using one of these methods:
+//    - audit:  listening for socket creation from auditd.
+//    - proc:   search /proc
+//
+// If the PID is not found by one of the 2 first methods, it'll try it using /proc.
+func GetPIDFromINode(inode int, inodeKey string) int {
+       found := -1
+       if inode <= 0 {
+               return found
+       }
+       start := time.Now()
+
+       expect := fmt.Sprintf("socket:[%d]", inode)
+       if cachedPidInode := inodesCache.getPid(inodeKey); cachedPidInode != -1 {
+               log.Debug("Inode found in cache: %v %v %v %v", time.Since(start), inodesCache.getPid(inodeKey), inode, inodeKey)
+               return cachedPidInode
+       }
+
+       cachedPid, pos := pidsCache.getPid(inode, inodeKey, expect)
+       if cachedPid != -1 {
+               log.Debug("Socket found in known pids %v, pid: %d, inode: %d, pos: %d, pids in cache: %d", time.Since(start), cachedPid, inode, pos, pidsCache.countItems())
+               pidsCache.sort(cachedPid)
+               inodesCache.add(inodeKey, "", cachedPid)
+               return cachedPid
+       }
+
+       if MethodIsAudit() {
+               if aPid, pos := getPIDFromAuditEvents(inode, inodeKey, expect); aPid != -1 {
+                       log.Debug("PID found via audit events: %v, position: %d", time.Since(start), pos)
+                       return aPid
+               }
+       }
+       if found == -1 || methodIsProc() {
+               found = lookupPidInProc("/proc/", expect, inodeKey, inode)
+       }
+       log.Debug("new pid lookup took (%d): %v", found, time.Since(start))
+
+       return found
+}
+
+// FindProcess checks if a process exists given a PID.
+// If it exists in /proc, a new Process{} object is returned with  the details
+// to identify a process (cmdline, name, environment variables, etc).
+func FindProcess(pid int, interceptUnknown bool) *Process {
+       if interceptUnknown && pid < 0 {
+               return NewProcess(0, "")
+       }
+
+       if proc := findProcessInActivePidsCache(uint64(pid)); proc != nil {
+               return proc
+       }
+
+       proc := NewProcess(pid, "")
+       if err := proc.GetInfo(); err != nil {
+               log.Debug("[%d] FindProcess() error: %s", pid, err)
+               return nil
+       }
+
+       AddToActivePidsCache(uint64(pid), proc)
+       return proc
+}
diff --git a/daemon/procmon/process.go b/daemon/procmon/process.go
new file mode 100644 (file)
index 0000000..f6a2a65
--- /dev/null
@@ -0,0 +1,164 @@
+package procmon
+
+import (
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+var (
+       cacheMonitorsRunning = false
+       lock                 = sync.RWMutex{}
+       monitorMethod        = MethodProc
+)
+
+// monitor method supported types
+const (
+       MethodProc  = "proc"
+       MethodAudit = "audit"
+       MethodEbpf  = "ebpf"
+
+       KernelConnection = "Kernel connection"
+       ProcSelf         = "/proc/self/"
+)
+
+// man 5 proc; man procfs
+type procIOstats struct {
+       RChar        int64
+       WChar        int64
+       SyscallRead  int64
+       SyscallWrite int64
+       ReadBytes    int64
+       WriteBytes   int64
+}
+
+type procNetStats struct {
+       ReadBytes  uint64
+       WriteBytes uint64
+}
+
+type procDescriptors struct {
+       ModTime time.Time
+       Name    string
+       SymLink string
+       Size    int64
+}
+
+type procStatm struct {
+       Size     int64
+       Resident int64
+       Shared   int64
+       Text     int64
+       Lib      int64
+       Data     int64 // data + stack
+       Dt       int
+}
+
+// Process holds the details of a process.
+type Process struct {
+       Env      map[string]string
+       IOStats  *procIOstats
+       NetStats *procNetStats
+       Statm    *procStatm
+       Maps     string
+       // Path is the absolute path to the binary
+       Path        string
+       Comm        string
+       CWD         string
+       Status      string
+       Stat        string
+       Stack       string
+       Descriptors []*procDescriptors
+       // Args is the command that the user typed. It MAY contain the absolute path
+       // of the binary:
+       // $ curl https://...
+       //   -> Path: /usr/bin/curl
+       //   -> Args: curl https://....
+       // $ /usr/bin/curl https://...
+       //   -> Path: /usr/bin/curl
+       //   -> Args: /usr/bin/curl https://....
+
+       Args []string
+       ID   int
+       PPID int
+       UID  int
+}
+
+// NewProcess returns a new Process structure.
+func NewProcess(pid int, comm string) *Process {
+       return &Process{
+               ID:       pid,
+               Comm:     comm,
+               Args:     make([]string, 0),
+               Env:      make(map[string]string),
+               IOStats:  &procIOstats{},
+               NetStats: &procNetStats{},
+               Statm:    &procStatm{},
+       }
+}
+
+// Serialize transforms a Process object to gRPC protocol object
+func (p *Process) Serialize() *protocol.Process {
+       ioStats := p.IOStats
+       netStats := p.NetStats
+       if ioStats == nil {
+               ioStats = &procIOstats{}
+       }
+       if netStats == nil {
+               netStats = &procNetStats{}
+       }
+       return &protocol.Process{
+               Pid:       uint64(p.ID),
+               Ppid:      uint64(p.PPID),
+               Uid:       uint64(p.UID),
+               Comm:      p.Comm,
+               Path:      p.Path,
+               Args:      p.Args,
+               Env:       p.Env,
+               Cwd:       p.CWD,
+               IoReads:   uint64(ioStats.RChar),
+               IoWrites:  uint64(ioStats.WChar),
+               NetReads:  netStats.ReadBytes,
+               NetWrites: netStats.WriteBytes,
+       }
+}
+
+// SetMonitorMethod configures a new method for parsing connections.
+func SetMonitorMethod(newMonitorMethod string) {
+       lock.Lock()
+       defer lock.Unlock()
+
+       monitorMethod = newMonitorMethod
+}
+
+// GetMonitorMethod configures a new method for parsing connections.
+func GetMonitorMethod() string {
+       lock.Lock()
+       defer lock.Unlock()
+
+       return monitorMethod
+}
+
+// MethodIsEbpf returns if the process monitor method is eBPF.
+func MethodIsEbpf() bool {
+       lock.RLock()
+       defer lock.RUnlock()
+
+       return monitorMethod == MethodEbpf
+}
+
+// MethodIsAudit returns if the process monitor method is eBPF.
+func MethodIsAudit() bool {
+       lock.RLock()
+       defer lock.RUnlock()
+
+       return monitorMethod == MethodAudit
+}
+
+func methodIsProc() bool {
+       lock.RLock()
+       defer lock.RUnlock()
+
+       return monitorMethod == MethodProc
+}
diff --git a/daemon/procmon/process_test.go b/daemon/procmon/process_test.go
new file mode 100644 (file)
index 0000000..dd9b2a8
--- /dev/null
@@ -0,0 +1,130 @@
+package procmon
+
+import (
+       "os"
+       "testing"
+)
+
+var (
+       myPid = os.Getpid()
+       proc  = NewProcess(myPid, "fakeComm")
+)
+
+func TestNewProcess(t *testing.T) {
+       if proc.ID != myPid {
+               t.Error("NewProcess PID not equal to ", myPid)
+       }
+       if proc.Comm != "fakeComm" {
+               t.Error("NewProcess Comm not equal to fakeComm")
+       }
+}
+
+func TestProcPath(t *testing.T) {
+       if err := proc.ReadPath(); err != nil {
+               t.Error("Proc path error:", err)
+       }
+       if proc.Path == "/fake/path" {
+               t.Error("Proc path equal to /fake/path, should be different:", proc.Path)
+       }
+}
+
+func TestProcCwd(t *testing.T) {
+       err := proc.ReadCwd()
+
+       if proc.CWD == "" {
+               t.Error("Proc readCwd() not read:", err)
+       }
+}
+
+func TestProcCmdline(t *testing.T) {
+       proc.ReadCmdline()
+
+       if len(proc.Args) == 0 {
+               t.Error("Proc Args should not be empty:", proc.Args)
+       }
+}
+
+func TestProcDescriptors(t *testing.T) {
+       proc.readDescriptors()
+
+       if len(proc.Descriptors) == 0 {
+               t.Error("Proc Descriptors should not be empty:", proc.Descriptors)
+       }
+}
+
+func TestProcEnv(t *testing.T) {
+       proc.ReadEnv()
+
+       if len(proc.Env) == 0 {
+               t.Error("Proc Env should not be empty:", proc.Env)
+       }
+}
+
+func TestProcIOStats(t *testing.T) {
+       proc.readIOStats()
+
+       if proc.IOStats.RChar == 0 {
+               t.Error("Proc.IOStats.RChar should not be 0:", proc.IOStats)
+       }
+       if proc.IOStats.WChar == 0 {
+               t.Error("Proc.IOStats.WChar should not be 0:", proc.IOStats)
+       }
+       if proc.IOStats.SyscallRead == 0 {
+               t.Error("Proc.IOStats.SyscallRead should not be 0:", proc.IOStats)
+       }
+       if proc.IOStats.SyscallWrite == 0 {
+               t.Error("Proc.IOStats.SyscallWrite should not be 0:", proc.IOStats)
+       }
+       /*if proc.IOStats.ReadBytes == 0 {
+               t.Error("Proc.IOStats.ReadBytes should not be 0:", proc.IOStats)
+       }
+       if proc.IOStats.WriteBytes == 0 {
+               t.Error("Proc.IOStats.WriteBytes should not be 0:", proc.IOStats)
+       }*/
+}
+
+func TestProcStatus(t *testing.T) {
+       proc.readStatus()
+
+       if proc.Status == "" {
+               t.Error("Proc Status should not be empty:", proc)
+       }
+       if proc.Stat == "" {
+               t.Error("Proc Stat should not be empty:", proc)
+       }
+       /*if proc.Stack == "" {
+               t.Error("Proc Stack should not be empty:", proc)
+       }*/
+       if proc.Maps == "" {
+               t.Error("Proc Maps should not be empty:", proc)
+       }
+       if proc.Statm.Size == 0 {
+               t.Error("Proc Statm Size should not be 0:", proc.Statm)
+       }
+       if proc.Statm.Resident == 0 {
+               t.Error("Proc Statm Resident should not be 0:", proc.Statm)
+       }
+       if proc.Statm.Shared == 0 {
+               t.Error("Proc Statm Shared should not be 0:", proc.Statm)
+       }
+       if proc.Statm.Text == 0 {
+               t.Error("Proc Statm Text should not be 0:", proc.Statm)
+       }
+       if proc.Statm.Lib != 0 {
+               t.Error("Proc Statm Lib should not be 0:", proc.Statm)
+       }
+       if proc.Statm.Data == 0 {
+               t.Error("Proc Statm Data should not be 0:", proc.Statm)
+       }
+       if proc.Statm.Dt != 0 {
+               t.Error("Proc Statm Dt should not be 0:", proc.Statm)
+       }
+}
+
+func TestProcCleanPath(t *testing.T) {
+       proc.Path = "/fake/path/binary (deleted)"
+       proc.CleanPath()
+       if proc.Path != "/fake/path/binary" {
+               t.Error("Proc cleanPath() not cleaned:", proc.Path)
+       }
+}
diff --git a/daemon/rule/loader.go b/daemon/rule/loader.go
new file mode 100644 (file)
index 0000000..dfa5f65
--- /dev/null
@@ -0,0 +1,439 @@
+package rule
+
+import (
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "path"
+       "path/filepath"
+       "sort"
+       "strings"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+
+       "github.com/fsnotify/fsnotify"
+)
+
+// Loader is the object that holds the rules loaded from disk, as well as the
+// rules watcher.
+type Loader struct {
+       rules     map[string]*Rule
+       watcher   *fsnotify.Watcher
+       path      string
+       rulesKeys []string
+       sync.RWMutex
+       liveReload        bool
+       liveReloadRunning bool
+}
+
+// NewLoader loads rules from disk, and watches for changes made to the rules files
+// on disk.
+func NewLoader(liveReload bool) (*Loader, error) {
+       watcher, err := fsnotify.NewWatcher()
+       if err != nil {
+               return nil, err
+       }
+       return &Loader{
+               path:              "",
+               rules:             make(map[string]*Rule),
+               liveReload:        liveReload,
+               watcher:           watcher,
+               liveReloadRunning: false,
+       }, nil
+}
+
+// NumRules returns he number of loaded rules.
+func (l *Loader) NumRules() int {
+       l.RLock()
+       defer l.RUnlock()
+       return len(l.rules)
+}
+
+// GetAll returns the loaded rules.
+func (l *Loader) GetAll() map[string]*Rule {
+       l.RLock()
+       defer l.RUnlock()
+       return l.rules
+}
+
+// Load loads rules files from disk.
+func (l *Loader) Load(path string) error {
+       if core.Exists(path) == false {
+               return fmt.Errorf("Path '%s' does not exist\nCreate it if you want to save rules to disk", path)
+       }
+       path, err := core.ExpandPath(path)
+       if err != nil {
+               return fmt.Errorf("Error accessing rules path: %s.\nCreate it if you want to save rules to disk", err)
+       }
+
+       expr := filepath.Join(path, "*.json")
+       matches, err := filepath.Glob(expr)
+       if err != nil {
+               return fmt.Errorf("Error globbing '%s': %s", expr, err)
+       }
+
+       l.path = path
+       if len(l.rules) == 0 {
+               l.rules = make(map[string]*Rule)
+       }
+
+       for _, fileName := range matches {
+               log.Debug("Reading rule from %s", fileName)
+
+               if err := l.loadRule(fileName); err != nil {
+                       log.Warning("%s", err)
+                       continue
+               }
+       }
+
+       if l.liveReload && l.liveReloadRunning == false {
+               go l.liveReloadWorker()
+       }
+
+       return nil
+}
+
+// Add adds a rule to the list of rules, and optionally saves it to disk.
+func (l *Loader) Add(rule *Rule, saveToDisk bool) error {
+       l.addUserRule(rule)
+       if saveToDisk {
+               fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
+               return l.Save(rule, fileName)
+       }
+       return nil
+}
+
+// Replace adds a rule to the list of rules, and optionally saves it to disk.
+func (l *Loader) Replace(rule *Rule, saveToDisk bool) error {
+       if err := l.replaceUserRule(rule); err != nil {
+               return err
+       }
+       if saveToDisk {
+               l.Lock()
+               defer l.Unlock()
+
+               fileName := filepath.Join(l.path, fmt.Sprintf("%s.json", rule.Name))
+               return l.Save(rule, fileName)
+       }
+       return nil
+}
+
+// Save a rule to disk.
+func (l *Loader) Save(rule *Rule, path string) error {
+       rule.Updated = time.Now().Format(time.RFC3339)
+       raw, err := json.MarshalIndent(rule, "", "  ")
+       if err != nil {
+               return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err)
+       }
+
+       if err = ioutil.WriteFile(path, raw, 0600); err != nil {
+               return fmt.Errorf("Error while saving rule %s to %s: %s", rule, path, err)
+       }
+
+       return nil
+}
+
+// Delete deletes a rule from the list by name.
+// If the duration is Always (i.e: saved on disk), it'll attempt to delete
+// it from disk.
+func (l *Loader) Delete(ruleName string) error {
+       l.Lock()
+       defer l.Unlock()
+
+       rule := l.rules[ruleName]
+       if rule == nil {
+               return nil
+       }
+       l.cleanListsRule(rule)
+
+       delete(l.rules, ruleName)
+       l.sortRules()
+
+       if rule.Duration != Always {
+               return nil
+       }
+
+       log.Info("Delete() rule: %s", rule)
+       return l.deleteRuleFromDisk(ruleName)
+}
+
+func (l *Loader) loadRule(fileName string) error {
+       raw, err := ioutil.ReadFile(fileName)
+       if err != nil {
+               return fmt.Errorf("Error while reading %s: %s", fileName, err)
+       }
+       l.Lock()
+       defer l.Unlock()
+
+       var r Rule
+       err = json.Unmarshal(raw, &r)
+       if err != nil {
+               return fmt.Errorf("Error parsing rule from %s: %s", fileName, err)
+       }
+       raw = nil
+
+       if oldRule, found := l.rules[r.Name]; found {
+               l.cleanListsRule(oldRule)
+       }
+
+       if !r.Enabled {
+               // XXX: we only parse and load the Data field if the rule is disabled and the Data field is not empty
+               // the rule will remain disabled.
+               if err = l.unmarshalOperatorList(&r.Operator); err != nil {
+                       return err
+               }
+       } else {
+               if err := r.Operator.Compile(); err != nil {
+                       log.Warning("Operator.Compile() error: %s: %s (%s)", err, r.Operator.Data, r.Name)
+                       return fmt.Errorf("(1) Error compiling rule: %s", err)
+               }
+               if r.Operator.Type == List {
+                       for i := 0; i < len(r.Operator.List); i++ {
+                               if err := r.Operator.List[i].Compile(); err != nil {
+                                       log.Warning("Operator.Compile() error: %s (%s)", err, r.Name)
+                                       return fmt.Errorf("(1) Error compiling list rule: %s", err)
+                               }
+                       }
+               }
+       }
+       if oldRule, found := l.rules[r.Name]; found {
+               l.deleteOldRuleFromDisk(oldRule, &r)
+       }
+
+       log.Debug("Loaded rule from %s: %s", fileName, r.String())
+       l.rules[r.Name] = &r
+       l.sortRules()
+
+       if l.isTemporary(&r) {
+               err = l.scheduleTemporaryRule(r)
+       }
+
+       return nil
+}
+
+// deleteRule deletes a rule from memory if it has been deleted from disk.
+// This is only called if fsnotify's Remove event is fired, thus it doesn't
+// have to delete temporary rules (!Always).
+func (l *Loader) deleteRule(filePath string) {
+       fileName := filepath.Base(filePath)
+       ruleName := fileName[:len(fileName)-5]
+
+       l.RLock()
+       rule, found := l.rules[ruleName]
+       delRule := found && rule.Duration == Always
+       l.RUnlock()
+       if delRule {
+               l.Delete(ruleName)
+       }
+}
+
+func (l *Loader) deleteRuleFromDisk(ruleName string) error {
+       path := fmt.Sprint(l.path, "/", ruleName, ".json")
+       return os.Remove(path)
+}
+
+// deleteOldRuleFromDisk deletes a rule from disk if the Duration changes
+// from Always (saved on disk), to !Always (temporary).
+func (l *Loader) deleteOldRuleFromDisk(oldRule, newRule *Rule) {
+       if oldRule.Duration == Always && newRule.Duration != Always {
+               if err := l.deleteRuleFromDisk(oldRule.Name); err != nil {
+                       log.Error("Error deleting old rule from disk: %s", oldRule.Name)
+               }
+       }
+}
+
+// cleanListsRule erases the list of domains of an Operator of type Lists
+func (l *Loader) cleanListsRule(oldRule *Rule) {
+       if oldRule.Operator.Type == Lists {
+               oldRule.Operator.StopMonitoringLists()
+       } else if oldRule.Operator.Type == List {
+               for i := 0; i < len(oldRule.Operator.List); i++ {
+                       if oldRule.Operator.List[i].Type == Lists {
+                               oldRule.Operator.List[i].StopMonitoringLists()
+                               break
+                       }
+               }
+       }
+}
+
+func (l *Loader) isTemporary(r *Rule) bool {
+       return r.Duration != Restart && r.Duration != Always && r.Duration != Once
+}
+
+func (l *Loader) isUniqueName(name string) bool {
+       _, found := l.rules[name]
+       return !found
+}
+
+func (l *Loader) setUniqueName(rule *Rule) {
+       l.Lock()
+       defer l.Unlock()
+
+       idx := 1
+       base := rule.Name
+       for l.isUniqueName(rule.Name) == false {
+               idx++
+               rule.Name = fmt.Sprintf("%s-%d", base, idx)
+       }
+}
+
+// Deprecated: rule.Operator.Data no longer holds the operator list in json format as string.
+func (l *Loader) unmarshalOperatorList(op *Operator) error {
+       if op.Type == List && len(op.List) == 0 && op.Data != "" {
+               if err := json.Unmarshal([]byte(op.Data), &op.List); err != nil {
+                       return fmt.Errorf("error loading rule of type list: %s", err)
+               }
+               op.Data = ""
+       }
+
+       return nil
+}
+
+func (l *Loader) sortRules() {
+       l.rulesKeys = make([]string, 0, len(l.rules))
+       for k := range l.rules {
+               l.rulesKeys = append(l.rulesKeys, k)
+       }
+       sort.Strings(l.rulesKeys)
+}
+
+func (l *Loader) addUserRule(rule *Rule) {
+       if rule.Duration == Once {
+               return
+       }
+
+       l.setUniqueName(rule)
+       l.replaceUserRule(rule)
+}
+
+func (l *Loader) replaceUserRule(rule *Rule) (err error) {
+       l.Lock()
+       oldRule, found := l.rules[rule.Name]
+       l.Unlock()
+
+       if found {
+               // If the rule has changed from Always (saved on disk) to !Always (temporary),
+               // we need to delete the rule from disk and keep it in memory.
+               l.deleteOldRuleFromDisk(oldRule, rule)
+
+               // delete loaded lists, if this is a rule of type Lists
+               l.cleanListsRule(oldRule)
+       }
+
+       if err := l.unmarshalOperatorList(&rule.Operator); err != nil {
+               log.Error(err.Error())
+       }
+
+       if rule.Enabled {
+               if err := rule.Operator.Compile(); err != nil {
+                       log.Warning("Operator.Compile() error: %s: %s", err, rule.Operator.Data)
+                       return fmt.Errorf("(2) error compiling rule: %s", err)
+               }
+
+               if rule.Operator.Type == List {
+                       for i := 0; i < len(rule.Operator.List); i++ {
+                               if err := rule.Operator.List[i].Compile(); err != nil {
+                                       log.Warning("Operator.Compile() error: %s: ", err)
+                                       return fmt.Errorf("(2) error compiling list rule: %s", err)
+                               }
+                       }
+               }
+       }
+       l.Lock()
+       l.rules[rule.Name] = rule
+       l.sortRules()
+       l.Unlock()
+
+       if l.isTemporary(rule) {
+               err = l.scheduleTemporaryRule(*rule)
+       }
+
+       return err
+}
+
+func (l *Loader) scheduleTemporaryRule(rule Rule) error {
+       tTime, err := time.ParseDuration(string(rule.Duration))
+       if err != nil {
+               return err
+       }
+
+       time.AfterFunc(tTime, func() {
+               l.Lock()
+               defer l.Unlock()
+
+               log.Info("Temporary rule expired: %s - %s", rule.Name, rule.Duration)
+               if newRule, found := l.rules[rule.Name]; found {
+                       if newRule.Duration != rule.Duration {
+                               log.Debug("%s temporary rule expired, but has new Duration, old: %s, new: %s", rule.Name, rule.Duration, newRule.Duration)
+                               return
+                       }
+                       delete(l.rules, rule.Name)
+                       l.sortRules()
+               }
+       })
+       return nil
+}
+
+func (l *Loader) liveReloadWorker() {
+       l.liveReloadRunning = true
+
+       log.Debug("Rules watcher started on path %s ...", l.path)
+       if err := l.watcher.Add(l.path); err != nil {
+               log.Error("Could not watch path: %s", err)
+               l.liveReloadRunning = false
+               return
+       }
+
+       for {
+               select {
+               case event := <-l.watcher.Events:
+                       // a new rule json file has been created or updated
+                       if event.Op&fsnotify.Write == fsnotify.Write {
+                               if strings.HasSuffix(event.Name, ".json") {
+                                       log.Important("Ruleset changed due to %s, reloading ...", path.Base(event.Name))
+                                       if err := l.loadRule(event.Name); err != nil {
+                                               log.Warning("%s", err)
+                                       }
+                               }
+                       } else if event.Op&fsnotify.Remove == fsnotify.Remove {
+                               if strings.HasSuffix(event.Name, ".json") {
+                                       log.Important("Rule deleted %s", path.Base(event.Name))
+                                       // we only need to delete from memory rules of type Always,
+                                       // because the Remove event is of a file, i.e.: Duration == Always
+                                       l.deleteRule(event.Name)
+                               }
+                       }
+               case err := <-l.watcher.Errors:
+                       log.Error("File system watcher error: %s", err)
+               }
+       }
+}
+
+// FindFirstMatch will try match the connection against the existing rule set.
+func (l *Loader) FindFirstMatch(con *conman.Connection) (match *Rule) {
+       l.RLock()
+       defer l.RUnlock()
+
+       for _, idx := range l.rulesKeys {
+               rule, _ := l.rules[idx]
+               if rule.Enabled == false {
+                       continue
+               }
+               if rule.Match(con) {
+                       // We have a match.
+                       // Save the rule in order to don't ask the user to take action,
+                       // and keep iterating until a Deny or a Priority rule appears.
+                       match = rule
+                       if rule.Action == Reject || rule.Action == Deny || rule.Precedence == true {
+                               return rule
+                       }
+               }
+       }
+
+       return match
+}
diff --git a/daemon/rule/loader_test.go b/daemon/rule/loader_test.go
new file mode 100644 (file)
index 0000000..37d958d
--- /dev/null
@@ -0,0 +1,327 @@
+package rule
+
+import (
+       "fmt"
+       "io"
+       "math/rand"
+       "os"
+       "testing"
+       "time"
+)
+
+var tmpDir string
+
+func TestMain(m *testing.M) {
+       tmpDir = "/tmp/ostest_" + randString()
+       os.Mkdir(tmpDir, 0777)
+       defer os.RemoveAll(tmpDir)
+       os.Exit(m.Run())
+}
+
+func TestRuleLoader(t *testing.T) {
+       t.Parallel()
+       t.Log("Test rules loader")
+
+       var list []Operator
+       dur1s := Duration("1s")
+       dummyOper, _ := NewOperator(Simple, false, OpTrue, "", list)
+       dummyOper.Compile()
+       inMem1sRule := Create("000-xxx-name", "rule description xxx", true, false, false, Allow, dur1s, dummyOper)
+       inMemUntilRestartRule := Create("000-aaa-name", "rule description aaa", true, false, false, Allow, Restart, dummyOper)
+
+       l, err := NewLoader(false)
+       if err != nil {
+               t.Fail()
+       }
+       if err = l.Load("/non/existent/path/"); err == nil {
+               t.Error("non existent path test: err should not be nil")
+       }
+
+       if err = l.Load("testdata/"); err != nil {
+               t.Error("Error loading test rules: ", err)
+       }
+       // we expect 6 valid rules (2 invalid), loaded from testdata/
+       testNumRules(t, l, 6)
+
+       if err = l.Add(inMem1sRule, false); err != nil {
+               t.Error("Error adding temporary rule")
+       }
+       testNumRules(t, l, 7)
+
+       // test auto deletion of temporary rule
+       time.Sleep(time.Second * 2)
+       testNumRules(t, l, 6)
+
+       if err = l.Add(inMemUntilRestartRule, false); err != nil {
+               t.Error("Error adding temporary rule (2)")
+       }
+       testNumRules(t, l, 7)
+       testRulesOrder(t, l)
+       testSortRules(t, l)
+       testFindMatch(t, l)
+       testFindEnabled(t, l)
+       testDurationChange(t, l)
+}
+
+func TestRuleLoaderInvalidRegexp(t *testing.T) {
+       t.Parallel()
+       t.Log("Test rules loader: invalid regexp")
+
+       l, err := NewLoader(true)
+       if err != nil {
+               t.Fail()
+       }
+       t.Run("loadRule() from disk test (simple)", func(t *testing.T) {
+               if err := l.loadRule("testdata/invalid-regexp.json"); err == nil {
+                       t.Error("invalid regexp rule loaded: loadRule()")
+               }
+       })
+
+       t.Run("loadRule() from disk test (list)", func(t *testing.T) {
+               if err := l.loadRule("testdata/invalid-regexp-list.json"); err == nil {
+                       t.Error("invalid regexp rule loaded: loadRule()")
+               }
+       })
+
+       var list []Operator
+       dur30m := Duration("30m")
+       opListData := `[{"type": "regexp", "operand": "process.path", "sensitive": false, "data": "^(/di(rmngr)$"}, {"type": "simple", "operand": "dest.port", "data": "53", "sensitive": false}]`
+       invalidRegexpOp, _ := NewOperator(List, false, OpList, opListData, list)
+       invalidRegexpRule := Create("invalid-regexp", "invalid rule description", true, false, false, Allow, dur30m, invalidRegexpOp)
+
+       t.Run("replaceUserRule() test list", func(t *testing.T) {
+               if err := l.replaceUserRule(invalidRegexpRule); err == nil {
+                       t.Error("invalid regexp rule loaded: replaceUserRule()")
+               }
+       })
+}
+
+// Test rules of type operator.list. There're these scenarios:
+// - Enabled rules:
+//    * operator Data field is ignored if it contains the list of operators as json string.
+//    * the operarots list is expanded as json objecs under "list": []
+// For new rules (> v1.6.3), Data field will be empty.
+//
+// - Disabled rules
+//    * (old) the Data field contains the list of operators as json string, and the list of operarots is empty.
+//    * Data field empty, and the list of operators expanded.
+// In all cases the list of operators must be loaded.
+func TestRuleLoaderList(t *testing.T) {
+       l, err := NewLoader(true)
+       if err != nil {
+               t.Fail()
+       }
+
+       testRules := map[string]string{
+               "rule-with-operator-list":                          "testdata/rule-operator-list.json",
+               "rule-disabled-with-operators-list-as-json-string": "testdata/rule-disabled-operator-list.json",
+               "rule-disabled-with-operators-list-expanded":       "testdata/rule-disabled-operator-list-expanded.json",
+               "rule-with-operator-list-data-empty":               "testdata/rule-operator-list-data-empty.json",
+       }
+
+       for name, path := range testRules {
+               t.Run(fmt.Sprint("loadRule() ", path), func(t *testing.T) {
+                       if err := l.loadRule(path); err != nil {
+                               t.Error(fmt.Sprint("loadRule() ", path, " error:"), err)
+                       }
+                       t.Log("Test: List rule:", name, path)
+                       r, found := l.rules[name]
+                       if !found {
+                               t.Error(fmt.Sprint("loadRule() ", path, " not in the list:"), l.rules)
+                       }
+                       // Starting from > v1.6.3, after loading a rule of type List, the field Operator.Data is emptied, if the Data contained the list of operators as json.
+                       if len(r.Operator.List) != 2 {
+                               t.Error(fmt.Sprint("loadRule() ", path, " operator List not loaded:"), r)
+                       }
+                       if r.Operator.List[0].Type != Simple ||
+                               r.Operator.List[0].Operand != OpProcessPath ||
+                               r.Operator.List[0].Data != "/usr/bin/telnet" {
+                               t.Error(fmt.Sprint("loadRule() ", path, " operator List 0 not loaded:"), r)
+                       }
+                       if r.Operator.List[1].Type != Simple ||
+                               r.Operator.List[1].Operand != OpDstPort ||
+                               r.Operator.List[1].Data != "53" {
+                               t.Error(fmt.Sprint("loadRule() ", path, " operator List 1 not loaded:"), r)
+                       }
+               })
+       }
+}
+
+func TestLiveReload(t *testing.T) {
+       t.Parallel()
+       t.Log("Test rules loader with live reload")
+       l, err := NewLoader(true)
+       if err != nil {
+               t.Fail()
+       }
+       if err = Copy("testdata/000-allow-chrome.json", tmpDir+"/000-allow-chrome.json"); err != nil {
+               t.Error("Error copying rule into a temp dir")
+       }
+       if err = Copy("testdata/001-deny-chrome.json", tmpDir+"/001-deny-chrome.json"); err != nil {
+               t.Error("Error copying rule into a temp dir")
+       }
+       if err = l.Load(tmpDir); err != nil {
+               t.Error("Error loading test rules: ", err)
+       }
+       //wait for watcher to activate
+       time.Sleep(time.Second)
+       if err = Copy("testdata/live_reload/test-live-reload-remove.json", tmpDir+"/test-live-reload-remove.json"); err != nil {
+               t.Error("Error copying rules into temp dir")
+       }
+       if err = Copy("testdata/live_reload/test-live-reload-delete.json", tmpDir+"/test-live-reload-delete.json"); err != nil {
+               t.Error("Error copying rules into temp dir")
+       }
+       //wait for watcher to pick up the changes
+       time.Sleep(time.Second)
+       testNumRules(t, l, 4)
+       if err = os.Remove(tmpDir + "/test-live-reload-remove.json"); err != nil {
+               t.Error("Error Remove()ing file from temp dir")
+       }
+       if err = l.Delete("test-live-reload-delete"); err != nil {
+               t.Error("Error Delete()ing file from temp dir")
+       }
+       //wait for watcher to pick up the changes
+       time.Sleep(time.Second)
+       testNumRules(t, l, 2)
+}
+
+func randString() string {
+       rand.Seed(time.Now().UnixNano())
+       var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
+       b := make([]rune, 10)
+       for i := range b {
+               b[i] = letterRunes[rand.Intn(len(letterRunes))]
+       }
+       return string(b)
+}
+
+func Copy(src, dst string) error {
+       in, err := os.Open(src)
+       if err != nil {
+               return err
+       }
+       defer in.Close()
+
+       out, err := os.Create(dst)
+       if err != nil {
+               return err
+       }
+       defer out.Close()
+
+       _, err = io.Copy(out, in)
+       if err != nil {
+               return err
+       }
+       return out.Close()
+}
+
+func testNumRules(t *testing.T, l *Loader, num int) {
+       if l.NumRules() != num {
+               t.Error("rules number should be (2): ", num)
+       }
+}
+
+func testRulesOrder(t *testing.T, l *Loader) {
+       if l.rulesKeys[0] != "000-aaa-name" {
+               t.Error("Rules not in order (0): ", l.rulesKeys)
+       }
+       if l.rulesKeys[1] != "000-allow-chrome" {
+               t.Error("Rules not in order (1): ", l.rulesKeys)
+       }
+       if l.rulesKeys[2] != "001-deny-chrome" {
+               t.Error("Rules not in order (2): ", l.rulesKeys)
+       }
+}
+
+func testSortRules(t *testing.T, l *Loader) {
+       l.rulesKeys[1] = "001-deny-chrome"
+       l.rulesKeys[2] = "000-allow-chrome"
+       l.sortRules()
+       if l.rulesKeys[1] != "000-allow-chrome" {
+               t.Error("Rules not in order (1): ", l.rulesKeys)
+       }
+       if l.rulesKeys[2] != "001-deny-chrome" {
+               t.Error("Rules not in order (2): ", l.rulesKeys)
+       }
+}
+
+func testFindMatch(t *testing.T, l *Loader) {
+       conn.Process.Path = "/opt/google/chrome/chrome"
+
+       testFindPriorityMatch(t, l)
+       testFindDenyMatch(t, l)
+       testFindAllowMatch(t, l)
+
+       restoreConnection()
+}
+
+func testFindPriorityMatch(t *testing.T, l *Loader) {
+       match := l.FindFirstMatch(conn)
+       if match == nil {
+               t.Error("FindPriorityMatch didn't match")
+       }
+       // test 000-allow-chrome, priority == true
+       if match.Name != "000-allow-chrome" {
+               t.Error("findPriorityMatch: priority rule failed: ", match)
+       }
+
+}
+
+func testFindDenyMatch(t *testing.T, l *Loader) {
+       l.rules["000-allow-chrome"].Precedence = false
+       // test 000-allow-chrome, priority == false
+       // 001-deny-chrome must match
+       match := l.FindFirstMatch(conn)
+       if match == nil {
+               t.Error("FindDenyMatch deny didn't match")
+       }
+       if match.Name != "001-deny-chrome" {
+               t.Error("findDenyMatch: deny rule failed: ", match)
+       }
+}
+
+func testFindAllowMatch(t *testing.T, l *Loader) {
+       l.rules["000-allow-chrome"].Precedence = false
+       l.rules["001-deny-chrome"].Action = Allow
+       // test 000-allow-chrome, priority == false
+       // 001-deny-chrome must match
+       match := l.FindFirstMatch(conn)
+       if match == nil {
+               t.Error("FindAllowMatch allow didn't match")
+       }
+       if match.Name != "001-deny-chrome" {
+               t.Error("findAllowMatch: allow rule failed: ", match)
+       }
+}
+
+func testFindEnabled(t *testing.T, l *Loader) {
+       l.rules["000-allow-chrome"].Precedence = false
+       l.rules["001-deny-chrome"].Action = Allow
+       l.rules["001-deny-chrome"].Enabled = false
+       // test 000-allow-chrome, priority == false
+       // 001-deny-chrome must match
+       match := l.FindFirstMatch(conn)
+       if match == nil {
+               t.Error("FindEnabledMatch, match nil")
+       }
+       if match.Name == "001-deny-chrome" {
+               t.Error("findEnabledMatch: deny rule shouldn't have matched: ", match)
+       }
+}
+
+// test that changing the Duration of a temporary rule doesn't delete
+// the new one, ignoring the old timer.
+func testDurationChange(t *testing.T, l *Loader) {
+       l.rules["000-aaa-name"].Duration = "2s"
+       if err := l.replaceUserRule(l.rules["000-aaa-name"]); err != nil {
+               t.Error("testDurationChange, error replacing rule: ", err)
+       }
+       l.rules["000-aaa-name"].Duration = "1h"
+       if err := l.replaceUserRule(l.rules["000-aaa-name"]); err != nil {
+               t.Error("testDurationChange, error replacing rule: ", err)
+       }
+       time.Sleep(time.Second * 4)
+       if _, found := l.rules["000-aaa-name"]; !found {
+               t.Error("testDurationChange, error: rule has been deleted")
+       }
+}
diff --git a/daemon/rule/operator.go b/daemon/rule/operator.go
new file mode 100644 (file)
index 0000000..021afc0
--- /dev/null
@@ -0,0 +1,319 @@
+package rule
+
+import (
+       "fmt"
+       "net"
+       "reflect"
+       "regexp"
+       "strconv"
+       "strings"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+// Type is the type of rule.
+// Every type has its own way of checking the user data against connections.
+type Type string
+
+// Sensitive defines if a rule is case-sensitive or not. By default no.
+type Sensitive bool
+
+// Operand is what we check on a connection.
+type Operand string
+
+// Available types
+const (
+       Simple  = Type("simple")
+       Regexp  = Type("regexp")
+       Complex = Type("complex") // for future use
+       List    = Type("list")
+       Network = Type("network")
+       Lists   = Type("lists")
+)
+
+// Available operands
+const (
+       OpTrue                = Operand("true")
+       OpProcessID           = Operand("process.id")
+       OpProcessPath         = Operand("process.path")
+       OpProcessCmd          = Operand("process.command")
+       OpProcessEnvPrefix    = Operand("process.env.")
+       OpProcessEnvPrefixLen = 12
+       OpUserID              = Operand("user.id")
+       OpSrcIP               = Operand("source.ip")
+       OpSrcPort             = Operand("source.port")
+       OpDstIP               = Operand("dest.ip")
+       OpDstHost             = Operand("dest.host")
+       OpDstPort             = Operand("dest.port")
+       OpDstNetwork          = Operand("dest.network")
+       OpSrcNetwork          = Operand("source.network")
+       OpProto               = Operand("protocol")
+       OpIfaceIn             = Operand("iface.in")
+       OpIfaceOut            = Operand("iface.out")
+       OpList                = Operand("list")
+       OpDomainsLists        = Operand("lists.domains")
+       OpDomainsRegexpLists  = Operand("lists.domains_regexp")
+       OpIPLists             = Operand("lists.ips")
+       OpNetLists            = Operand("lists.nets")
+)
+
+type opCallback func(value interface{}) bool
+
+// Operator represents what we want to filter of a connection, and how.
+type Operator struct {
+       cb              opCallback
+       re              *regexp.Regexp
+       netMask         *net.IPNet
+       lists           map[string]interface{}
+       exitMonitorChan chan (bool)
+
+       Operand   Operand    `json:"operand"`
+       Data      string     `json:"data"`
+       Type      Type       `json:"type"`
+       List      []Operator `json:"list"`
+       Sensitive Sensitive  `json:"sensitive"`
+
+       listsMonitorRunning bool
+       isCompiled          bool
+
+       sync.RWMutex
+}
+
+// NewOperator returns a new operator object
+func NewOperator(t Type, s Sensitive, o Operand, data string, list []Operator) (*Operator, error) {
+       op := Operator{
+               Type:      t,
+               Sensitive: s,
+               Operand:   o,
+               Data:      data,
+               List:      list,
+       }
+       return &op, nil
+}
+
+// Compile translates the operator type field to its callback counterpart
+func (o *Operator) Compile() error {
+       if o.isCompiled {
+               return nil
+       }
+       if o.Type == Simple {
+               o.cb = o.simpleCmp
+       } else if o.Type == Regexp {
+               o.cb = o.reCmp
+               if o.Sensitive == false {
+                       o.Data = strings.ToLower(o.Data)
+               }
+               re, err := regexp.Compile(o.Data)
+               if err != nil {
+                       return err
+               }
+               o.re = re
+       } else if o.Type == List {
+               o.Operand = OpList
+       } else if o.Type == Network {
+               var err error
+               _, o.netMask, err = net.ParseCIDR(o.Data)
+               if err != nil {
+                       return err
+               }
+               o.cb = o.cmpNetwork
+       } else if o.Type == Lists {
+               if o.Data == "" {
+                       return fmt.Errorf("Operand lists is empty, nothing to load: %s", o)
+               }
+
+               if o.Operand == OpDomainsLists {
+                       o.loadLists()
+                       o.cb = o.domainsListCmp
+               } else if o.Operand == OpDomainsRegexpLists {
+                       o.loadLists()
+                       o.cb = o.reListCmp
+               } else if o.Operand == OpIPLists {
+                       o.loadLists()
+                       o.cb = o.ipListCmp
+               } else if o.Operand == OpNetLists {
+                       o.loadLists()
+                       o.cb = o.ipNetCmp
+               } else {
+                       return fmt.Errorf("Unknown Lists operand: %s", o.Operand)
+               }
+
+       } else {
+               return fmt.Errorf("Unknown type: %s", o.Type)
+       }
+
+       log.Debug("Operator compiled: %s", o)
+       o.isCompiled = true
+
+       return nil
+}
+
+func (o *Operator) String() string {
+       how := "is"
+       if o.Type == Regexp {
+               how = "matches"
+       }
+       return fmt.Sprintf("%s %s '%s'", log.Bold(string(o.Operand)), how, log.Yellow(string(o.Data)))
+}
+
+func (o *Operator) simpleCmp(v interface{}) bool {
+       if o.Sensitive == false {
+               return strings.EqualFold(v.(string), o.Data)
+       }
+       return v == o.Data
+}
+
+func (o *Operator) reCmp(v interface{}) bool {
+       if vt := reflect.ValueOf(v).Kind(); vt != reflect.String {
+               log.Warning("Operator.reCmp() bad interface type: %T", v)
+               return false
+       }
+       if o.Sensitive == false {
+               v = strings.ToLower(v.(string))
+       }
+       return o.re.MatchString(v.(string))
+}
+
+func (o *Operator) cmpNetwork(destIP interface{}) bool {
+       // 192.0.2.1/24, 2001:db8:a0b:12f0::1/32
+       if o.netMask == nil {
+               log.Warning("cmpNetwork() NULL: %s", destIP)
+               return false
+       }
+       return o.netMask.Contains(destIP.(net.IP))
+}
+
+func (o *Operator) domainsListCmp(v interface{}) bool {
+       dstHost := v.(string)
+       if dstHost == "" {
+               return false
+       }
+       if o.Sensitive == false {
+               dstHost = strings.ToLower(dstHost)
+       }
+       o.RLock()
+       defer o.RUnlock()
+
+       if _, found := o.lists[dstHost]; found {
+               log.Debug("%s: %s, %s", log.Red("domain list match"), dstHost, o.lists[dstHost])
+               return true
+       }
+       return false
+}
+
+func (o *Operator) ipListCmp(v interface{}) bool {
+       dstIP := v.(string)
+       if dstIP == "" {
+               return false
+       }
+       o.RLock()
+       defer o.RUnlock()
+
+       if _, found := o.lists[dstIP]; found {
+               log.Debug("%s: %s, %s", log.Red("IP list match"), dstIP, o.lists[dstIP].(string))
+               return true
+       }
+       return false
+}
+
+func (o *Operator) ipNetCmp(dstIP interface{}) bool {
+       o.RLock()
+       defer o.RUnlock()
+
+       for host, netMask := range o.lists {
+               n := netMask.(*net.IPNet)
+               if n.Contains(dstIP.(net.IP)) {
+                       log.Debug("%s: %s, %s", log.Red("Net list match"), dstIP, host)
+                       return true
+               }
+       }
+       return false
+}
+
+func (o *Operator) reListCmp(v interface{}) bool {
+       dstHost := v.(string)
+       if dstHost == "" {
+               return false
+       }
+       if o.Sensitive == false {
+               dstHost = strings.ToLower(dstHost)
+       }
+       o.RLock()
+       defer o.RUnlock()
+
+       for file, re := range o.lists {
+               r := re.(*regexp.Regexp)
+               if r.MatchString(dstHost) {
+                       log.Debug("%s: %s, %s", log.Red("Regexp list match"), dstHost, file)
+                       return true
+               }
+       }
+       return false
+}
+
+func (o *Operator) listMatch(con interface{}) bool {
+       res := true
+       for i := 0; i < len(o.List); i++ {
+               res = res && o.List[i].Match(con.(*conman.Connection))
+       }
+       return res
+}
+
+// Match tries to match parts of a connection with the given operator.
+func (o *Operator) Match(con *conman.Connection) bool {
+
+       if o.Operand == OpTrue {
+               return true
+       } else if o.Operand == OpList {
+               return o.listMatch(con)
+       } else if o.Operand == OpProcessPath {
+               return o.cb(con.Process.Path)
+       } else if o.Operand == OpProcessCmd {
+               return o.cb(strings.Join(con.Process.Args, " "))
+       } else if o.Operand == OpDstHost && con.DstHost != "" {
+               return o.cb(con.DstHost)
+       } else if o.Operand == OpDstIP {
+               return o.cb(con.DstIP.String())
+       } else if o.Operand == OpDstPort {
+               return o.cb(strconv.FormatUint(uint64(con.DstPort), 10))
+       } else if o.Operand == OpDomainsLists {
+               return o.cb(con.DstHost)
+       } else if o.Operand == OpIPLists {
+               return o.cb(con.DstIP.String())
+       } else if o.Operand == OpUserID {
+               return o.cb(strconv.Itoa(con.Entry.UserId))
+       } else if o.Operand == OpDstNetwork {
+               return o.cb(con.DstIP)
+       } else if o.Operand == OpSrcNetwork {
+               return o.cb(con.SrcIP)
+       } else if o.Operand == OpNetLists {
+               return o.cb(con.DstIP)
+       } else if o.Operand == OpDomainsRegexpLists {
+               return o.cb(con.DstHost)
+       } else if o.Operand == OpIfaceIn {
+               if ifname, err := net.InterfaceByIndex(con.Pkt.IfaceInIdx); err == nil {
+                       return o.cb(ifname.Name)
+               }
+       } else if o.Operand == OpIfaceOut {
+               if ifname, err := net.InterfaceByIndex(con.Pkt.IfaceOutIdx); err == nil {
+                       return o.cb(ifname.Name)
+               }
+       } else if o.Operand == OpProto {
+               return o.cb(con.Protocol)
+       } else if o.Operand == OpSrcIP {
+               return o.cb(con.SrcIP.String())
+       } else if o.Operand == OpSrcPort {
+               return o.cb(strconv.FormatUint(uint64(con.SrcPort), 10))
+       } else if o.Operand == OpProcessID {
+               return o.cb(strconv.Itoa(con.Process.ID))
+       } else if strings.HasPrefix(string(o.Operand), string(OpProcessEnvPrefix)) {
+               envVarName := core.Trim(string(o.Operand[OpProcessEnvPrefixLen:]))
+               envVarValue, _ := con.Process.Env[envVarName]
+               return o.cb(envVarValue)
+       }
+
+       return false
+}
diff --git a/daemon/rule/operator_lists.go b/daemon/rule/operator_lists.go
new file mode 100644 (file)
index 0000000..dd05635
--- /dev/null
@@ -0,0 +1,263 @@
+package rule
+
+import (
+       "fmt"
+       "io/ioutil"
+       "net"
+       "path/filepath"
+       "regexp"
+       "runtime/debug"
+       "strings"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+)
+
+func (o *Operator) monitorLists() {
+       log.Info("monitor lists started: %s", o.Data)
+
+       modTimes := make(map[string]time.Time)
+       totalFiles := 0
+       needReload := false
+       numFiles := 0
+
+       expr := filepath.Join(o.Data, "/*.*")
+       for {
+               select {
+               case <-o.exitMonitorChan:
+                       goto Exit
+               default:
+                       fileList, err := filepath.Glob(expr)
+                       if err != nil {
+                               log.Warning("Error reading directory of domains list: %s, %s", o.Data, err)
+                               goto Exit
+                       }
+                       numFiles = 0
+
+                       for _, filename := range fileList {
+                               // ignore hidden files
+                               name := filepath.Base(filename)
+                               if name[:1] == "." {
+                                       delete(modTimes, filename)
+                                       continue
+                               }
+                               // an overwrite operation performs two tasks: truncate the file and save the new content,
+                               // causing the file time to be modified twice.
+                               modTime, err := core.GetFileModTime(filename)
+                               if err != nil {
+                                       log.Debug("deleting saved mod time due to error reading the list, %s", filename)
+                                       delete(modTimes, filename)
+                               } else if lastModTime, found := modTimes[filename]; found {
+                                       if lastModTime.Equal(modTime) == false {
+                                               log.Debug("list changed: %s, %s, %s", lastModTime, modTime, filename)
+                                               needReload = true
+                                       }
+                               }
+                               modTimes[filename] = modTime
+                               numFiles++
+                       }
+                       fileList = nil
+
+                       if numFiles != totalFiles {
+                               needReload = true
+                       }
+                       totalFiles = numFiles
+
+                       if needReload {
+                               // we can't reload a single list, because the domains of all lists are added to the same map.
+                               // we could have the domains separated by lists/files, but then we'd need to iterate the map in order
+                               // to match a domain. Reloading the lists shoud only occur once a day.
+                               if err := o.readLists(); err != nil {
+                                       log.Warning("%s", err)
+                               }
+                               needReload = false
+                       }
+                       time.Sleep(4 * time.Second)
+               }
+       }
+
+Exit:
+       modTimes = nil
+       o.ClearLists()
+       log.Info("lists monitor stopped")
+}
+
+// ClearLists deletes all the entries of a list
+func (o *Operator) ClearLists() {
+       o.Lock()
+       defer o.Unlock()
+
+       log.Info("clearing domains lists: %d - %s", len(o.lists), o.Data)
+       for k := range o.lists {
+               delete(o.lists, k)
+       }
+       debug.FreeOSMemory()
+}
+
+// StopMonitoringLists stops the monitoring lists goroutine.
+func (o *Operator) StopMonitoringLists() {
+       if o.listsMonitorRunning == true {
+               o.exitMonitorChan <- true
+               o.exitMonitorChan = nil
+               o.listsMonitorRunning = false
+       }
+}
+
+func (o *Operator) readDomainsList(raw, fileName string) (dups uint64) {
+       log.Debug("Loading domains list: %s, size: %d", fileName, len(raw))
+       lines := strings.Split(string(raw), "\n")
+       for _, domain := range lines {
+               if len(domain) < 9 {
+                       continue
+               }
+               // exclude not valid lines
+               if domain[:7] != "0.0.0.0" && domain[:9] != "127.0.0.1" {
+                       continue
+               }
+               host := domain[8:]
+               // exclude localhost entries
+               if domain[:9] == "127.0.0.1" {
+                       host = domain[10:]
+               }
+               if host == "local" || host == "localhost" || host == "localhost.localdomain" || host == "broadcasthost" {
+                       continue
+               }
+
+               host = core.Trim(host)
+               if _, found := o.lists[host]; found {
+                       dups++
+                       continue
+               }
+               o.lists[host] = fileName
+       }
+       lines = nil
+       log.Info("%d domains loaded, %s", len(o.lists), fileName)
+
+       return dups
+}
+
+func (o *Operator) readNetList(raw, fileName string) (dups uint64) {
+       log.Debug("Loading nets list: %s, size: %d", fileName, len(raw))
+       lines := strings.Split(string(raw), "\n")
+       for _, line := range lines {
+               if line == "" || line[0] == '#' {
+                       continue
+               }
+               host := core.Trim(line)
+               if _, found := o.lists[host]; found {
+                       dups++
+                       continue
+               }
+               _, netMask, err := net.ParseCIDR(host)
+               if err != nil {
+                       log.Warning("Error parsing net from list: %s, (%s)", err, fileName)
+                       continue
+               }
+               o.lists[host] = netMask
+       }
+       lines = nil
+       log.Info("%d nets loaded, %s", len(o.lists), fileName)
+
+       return dups
+}
+
+func (o *Operator) readRegexpList(raw, fileName string) (dups uint64) {
+       log.Debug("Loading regexp list: %s, size: %d", fileName, len(raw))
+       lines := strings.Split(string(raw), "\n")
+       for n, line := range lines {
+               if line == "" || line[0] == '#' {
+                       continue
+               }
+               host := core.Trim(line)
+               if _, found := o.lists[host]; found {
+                       dups++
+                       continue
+               }
+               re, err := regexp.Compile(line)
+               if err != nil {
+                       log.Warning("Error compiling regexp from list: %s, (%d:%s)", err, n, fileName)
+                       continue
+               }
+               o.lists[line] = re
+       }
+       lines = nil
+       log.Info("%d regexps loaded, %s", len(o.lists), fileName)
+
+       return dups
+}
+
+func (o *Operator) readIPList(raw, fileName string) (dups uint64) {
+       log.Debug("Loading IPs list: %s, size: %d", fileName, len(raw))
+       lines := strings.Split(string(raw), "\n")
+       for _, line := range lines {
+               if line == "" || line[0] == '#' {
+                       continue
+               }
+               ip := core.Trim(line)
+               if _, found := o.lists[ip]; found {
+                       dups++
+                       continue
+               }
+               o.lists[ip] = fileName
+       }
+       lines = nil
+       log.Info("%d IPs loaded, %s", len(o.lists), fileName)
+
+       return dups
+}
+
+func (o *Operator) readLists() error {
+       o.ClearLists()
+
+       var dups uint64
+       // this list is particular to this operator and rule
+       o.Lock()
+       defer o.Unlock()
+       o.lists = make(map[string]interface{})
+
+       expr := filepath.Join(o.Data, "*.*")
+       fileList, err := filepath.Glob(expr)
+       if err != nil {
+               return fmt.Errorf("Error loading domains lists '%s': %s", expr, err)
+       }
+
+       for _, fileName := range fileList {
+               // ignore hidden files
+               name := filepath.Base(fileName)
+               if name[:1] == "." {
+                       continue
+               }
+
+               raw, err := ioutil.ReadFile(fileName)
+               if err != nil {
+                       log.Warning("Error reading list of IPs (%s): %s", fileName, err)
+                       continue
+               }
+
+               if o.Operand == OpDomainsLists {
+                       dups += o.readDomainsList(string(raw), fileName)
+               } else if o.Operand == OpDomainsRegexpLists {
+                       dups += o.readRegexpList(string(raw), fileName)
+               } else if o.Operand == OpNetLists {
+                       dups += o.readNetList(string(raw), fileName)
+               } else if o.Operand == OpIPLists {
+                       dups += o.readIPList(string(raw), fileName)
+               } else {
+                       log.Warning("Unknown lists operand type: %s", o.Operand)
+               }
+       }
+       log.Info("%d lists loaded, %d domains, %d duplicated", len(fileList), len(o.lists), dups)
+       return nil
+}
+
+func (o *Operator) loadLists() {
+       log.Info("loading domains lists: %s, %s, %s", o.Type, o.Operand, o.Data)
+
+       // when loading from disk, we don't use the Operator's constructor, so we need to create this channel
+       if o.exitMonitorChan == nil {
+               o.exitMonitorChan = make(chan bool)
+               o.listsMonitorRunning = true
+               go o.monitorLists()
+       }
+}
diff --git a/daemon/rule/operator_test.go b/daemon/rule/operator_test.go
new file mode 100644 (file)
index 0000000..5d7d07b
--- /dev/null
@@ -0,0 +1,742 @@
+package rule
+
+import (
+       "encoding/json"
+       "fmt"
+       "net"
+       "testing"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/netstat"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+)
+
+var (
+       defaultProcPath = "/usr/bin/opensnitchd"
+       defaultProcArgs = "-rules-path /etc/opensnitchd/rules/"
+       defaultDstHost  = "opensnitch.io"
+       defaultDstPort  = uint(443)
+       defaultDstIP    = "185.53.178.14"
+       defaultUserID   = 666
+
+       netEntry = &netstat.Entry{
+               UserId: defaultUserID,
+       }
+
+       proc = &procmon.Process{
+               ID:   12345,
+               Path: defaultProcPath,
+               Args: []string{"-rules-path", "/etc/opensnitchd/rules/"},
+       }
+
+       conn = &conman.Connection{
+               Protocol: "TCP",
+               SrcPort:  66666,
+               SrcIP:    net.ParseIP("192.168.1.111"),
+               DstIP:    net.ParseIP(defaultDstIP),
+               DstPort:  defaultDstPort,
+               DstHost:  defaultDstHost,
+               Process:  proc,
+               Entry:    netEntry,
+       }
+)
+
+func compileListOperators(list *[]Operator, t *testing.T) {
+       op := *list
+       for i := 0; i < len(*list); i++ {
+               if err := op[i].Compile(); err != nil {
+                       t.Error("NewOperator List, Compile() subitem error:", err)
+               }
+       }
+}
+
+func unmarshalListData(data string, t *testing.T) (op *[]Operator) {
+       if err := json.Unmarshal([]byte(data), &op); err != nil {
+               t.Error("Error unmarshalling list data:", err, data)
+               return nil
+       }
+       return op
+}
+
+func restoreConnection() {
+       conn.Process.Path = defaultProcPath
+       conn.DstHost = defaultDstHost
+       conn.DstPort = defaultDstPort
+       conn.Entry.UserId = defaultUserID
+}
+
+func TestNewOperatorSimple(t *testing.T) {
+       t.Log("Test NewOperator() simple")
+       var list []Operator
+
+       opSimple, err := NewOperator(Simple, false, OpTrue, "", list)
+       if err != nil {
+               t.Error("NewOperator simple.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opSimple.Compile(); err != nil {
+               t.Fail()
+       }
+       if opSimple.Match(nil) == false {
+               t.Error("Test NewOperator() simple.case-insensitive doesn't match")
+               t.Fail()
+       }
+
+       t.Run("Operator Simple proc.id", func(t *testing.T) {
+               // proc.id not sensitive
+               opSimple, err = NewOperator(Simple, false, OpProcessID, "12345", list)
+               if err != nil {
+                       t.Error("NewOperator simple.case-insensitive.proc.id err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple.case-insensitive.proc.id Compile() err:", err)
+                       t.Fail()
+               }
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple proc.id doesn't match")
+                       t.Fail()
+               }
+       })
+
+       opSimple, err = NewOperator(Simple, false, OpProcessPath, defaultProcPath, list)
+       t.Run("Operator Simple proc.path case-insensitive", func(t *testing.T) {
+               // proc path not sensitive
+               if err != nil {
+                       t.Error("NewOperator simple proc.path err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple.case-insensitive.proc.path Compile() err:", err)
+                       t.Fail()
+               }
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple proc.path doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator Simple proc.path sensitive", func(t *testing.T) {
+               // proc path sensitive
+               opSimple.Sensitive = true
+               conn.Process.Path = "/usr/bin/OpenSnitchd"
+               if opSimple.Match(conn) == true {
+                       t.Error("Test NewOperator() simple proc.path sensitive match")
+                       t.Fail()
+               }
+       })
+
+       opSimple, err = NewOperator(Simple, false, OpDstHost, defaultDstHost, list)
+       t.Run("Operator Simple con.dstHost case-insensitive", func(t *testing.T) {
+               // proc dst host not sensitive
+               if err != nil {
+                       t.Error("NewOperator simple proc.path err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple.case-insensitive.dstHost Compile() err:", err)
+                       t.Fail()
+               }
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple.conn.dstHost.not-sensitive doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator Simple con.dstHost case-insensitive different host", func(t *testing.T) {
+               conn.DstHost = "www.opensnitch.io"
+               if opSimple.Match(conn) == true {
+                       t.Error("Test NewOperator() simple.conn.dstHost.not-sensitive doesn't MATCH")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator Simple con.dstHost sensitive", func(t *testing.T) {
+               // proc dst host sensitive
+               opSimple, err = NewOperator(Simple, true, OpDstHost, "OpEnsNitCh.io", list)
+               if err != nil {
+                       t.Error("NewOperator simple.dstHost.sensitive err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple.dstHost.sensitive Compile() err:", err)
+                       t.Fail()
+               }
+               conn.DstHost = "OpEnsNitCh.io"
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple.dstHost.sensitive doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator Simple proc.args case-insensitive", func(t *testing.T) {
+               // proc args case-insensitive
+               opSimple, err = NewOperator(Simple, false, OpProcessCmd, defaultProcArgs, list)
+               if err != nil {
+                       t.Error("NewOperator simple proc.args err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple proc.args Compile() err: ", err)
+                       t.Fail()
+               }
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple proc.args doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator Simple con.dstIp case-insensitive", func(t *testing.T) {
+               // proc dstIp case-insensitive
+               opSimple, err = NewOperator(Simple, false, OpDstIP, defaultDstIP, list)
+               if err != nil {
+                       t.Error("NewOperator simple conn.dstip.err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple con.dstIp Compile() err: ", err)
+                       t.Fail()
+               }
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple conn.dstip doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator Simple UserId case-insensitive", func(t *testing.T) {
+               // conn.uid case-insensitive
+               opSimple, err = NewOperator(Simple, false, OpUserID, fmt.Sprint(defaultUserID), list)
+               if err != nil {
+                       t.Error("NewOperator simple conn.userid.err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opSimple.Compile(); err != nil {
+                       t.Error("NewOperator simple UserId Compile() err: ", err)
+                       t.Fail()
+               }
+               if opSimple.Match(conn) == false {
+                       t.Error("Test NewOperator() simple conn.userid doesn't match")
+                       t.Fail()
+               }
+       })
+
+       restoreConnection()
+}
+
+func TestNewOperatorNetwork(t *testing.T) {
+       t.Log("Test NewOperator() network")
+       var dummyList []Operator
+
+       opSimple, err := NewOperator(Network, false, OpDstNetwork, "185.53.178.14/24", dummyList)
+       if err != nil {
+               t.Error("NewOperator network.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opSimple.Compile(); err != nil {
+               t.Fail()
+       }
+       if opSimple.Match(conn) == false {
+               t.Error("Test NewOperator() network doesn't match")
+               t.Fail()
+       }
+
+       opSimple, err = NewOperator(Network, false, OpDstNetwork, "8.8.8.8/24", dummyList)
+       if err != nil {
+               t.Error("NewOperator network.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opSimple.Compile(); err != nil {
+               t.Fail()
+       }
+       if opSimple.Match(conn) == true {
+               t.Error("Test NewOperator() network doesn't match:", conn.DstIP)
+               t.Fail()
+       }
+
+       restoreConnection()
+}
+
+func TestNewOperatorRegexp(t *testing.T) {
+       t.Log("Test NewOperator() regexp")
+       var dummyList []Operator
+
+       opRE, err := NewOperator(Regexp, false, OpProto, "^TCP$", dummyList)
+       if err != nil {
+               t.Error("NewOperator regexp.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opRE.Compile(); err != nil {
+               t.Fail()
+       }
+       if opRE.Match(conn) == false {
+               t.Error("Test NewOperator() regexp doesn't match")
+               t.Fail()
+       }
+
+       restoreConnection()
+}
+
+func TestNewOperatorInvalidRegexp(t *testing.T) {
+       t.Log("Test NewOperator() invalid regexp")
+       var dummyList []Operator
+
+       opRE, err := NewOperator(Regexp, false, OpProto, "^TC(P$", dummyList)
+       if err != nil {
+               t.Error("NewOperator regexp.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opRE.Compile(); err == nil {
+               t.Error("NewOperator() invalid regexp. It should fail: ", err)
+               t.Fail()
+       }
+
+       restoreConnection()
+}
+
+func TestNewOperatorRegexpSensitive(t *testing.T) {
+       t.Log("Test NewOperator() regexp sensitive")
+       var dummyList []Operator
+
+       var sensitive Sensitive
+       sensitive = true
+
+       conn.Process.Path = "/tmp/cUrL"
+
+       opRE, err := NewOperator(Regexp, sensitive, OpProcessPath, "^/tmp/cUrL$", dummyList)
+       if err != nil {
+               t.Error("NewOperator regexp.case-sensitive.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opRE.Compile(); err != nil {
+               t.Fail()
+       }
+       if opRE.Match(conn) == false {
+               t.Error("Test NewOperator() RE sensitive doesn't match:", conn.Process.Path)
+               t.Fail()
+       }
+
+       t.Run("Operator regexp proc.path case-sensitive", func(t *testing.T) {
+               conn.Process.Path = "/tmp/curl"
+               if opRE.Match(conn) == true {
+                       t.Error("Test NewOperator() RE sensitive match:", conn.Process.Path)
+                       t.Fail()
+               }
+       })
+
+       opRE, err = NewOperator(Regexp, !sensitive, OpProcessPath, "^/tmp/cUrL$", dummyList)
+       if err != nil {
+               t.Error("NewOperator regexp.case-insensitive.err should be nil: ", err)
+               t.Fail()
+       }
+       if err = opRE.Compile(); err != nil {
+               t.Fail()
+       }
+       if opRE.Match(conn) == false {
+               t.Error("Test NewOperator() RE not sensitive match:", conn.Process.Path)
+               t.Fail()
+       }
+
+       restoreConnection()
+}
+
+func TestNewOperatorList(t *testing.T) {
+       t.Log("Test NewOperator() List")
+       var list []Operator
+       listData := `[{"type": "simple", "operand": "dest.ip", "data": "185.53.178.14", "sensitive": false}, {"type": "simple", "operand": "dest.port", "data": "443", "sensitive": false}]`
+
+       // simple list
+       opList, err := NewOperator(List, false, OpProto, listData, list)
+       t.Run("Operator List simple case-insensitive", func(t *testing.T) {
+               if err != nil {
+                       t.Error("NewOperator list.regexp.err should be nil: ", err)
+                       t.Fail()
+               }
+               if err = opList.Compile(); err != nil {
+                       t.Fail()
+               }
+               opList.List = *unmarshalListData(opList.Data, t)
+               compileListOperators(&opList.List, t)
+               if opList.Match(conn) == false {
+                       t.Error("Test NewOperator() list simple doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator List regexp case-insensitive", func(t *testing.T) {
+               // list with regexp, case-insensitive
+               listData = `[{"type": "regexp", "operand": "process.path", "data": "^/usr/bin/.*", "sensitive": false},{"type": "simple", "operand": "dest.ip", "data": "185.53.178.14", "sensitive": false}, {"type": "simple", "operand": "dest.port", "data": "443", "sensitive": false}]`
+               opList.List = *unmarshalListData(listData, t)
+               compileListOperators(&opList.List, t)
+               if err = opList.Compile(); err != nil {
+                       t.Fail()
+               }
+               if opList.Match(conn) == false {
+                       t.Error("Test NewOperator() list regexp doesn't match")
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator List regexp case-sensitive", func(t *testing.T) {
+               // list with regexp, case-sensitive
+               // "data": "^/usr/BiN/.*" must match conn.Process.Path (sensitive)
+               listData = `[{"type": "regexp", "operand": "process.path", "data": "^/usr/BiN/.*", "sensitive": false},{"type": "simple", "operand": "dest.ip", "data": "185.53.178.14", "sensitive": false}, {"type": "simple", "operand": "dest.port", "data": "443", "sensitive": false}]`
+               opList.List = *unmarshalListData(listData, t)
+               compileListOperators(&opList.List, t)
+               conn.Process.Path = "/usr/BiN/opensnitchd"
+               opList.Sensitive = true
+               if err = opList.Compile(); err != nil {
+                       t.Fail()
+               }
+               if opList.Match(conn) == false {
+                       t.Error("Test NewOperator() list.regexp.sensitive doesn't match:", conn.Process.Path)
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator List regexp case-insensitive 2", func(t *testing.T) {
+               // "data": "^/usr/BiN/.*" must not match conn.Process.Path (insensitive)
+               opList.Sensitive = false
+               conn.Process.Path = "/USR/BiN/opensnitchd"
+               if err = opList.Compile(); err != nil {
+                       t.Fail()
+               }
+               if opList.Match(conn) == false {
+                       t.Error("Test NewOperator() list.regexp.insensitive match:", conn.Process.Path)
+                       t.Fail()
+               }
+       })
+
+       t.Run("Operator List regexp case-insensitive 3", func(t *testing.T) {
+               // "data": "^/usr/BiN/.*" must match conn.Process.Path (insensitive)
+               opList.Sensitive = false
+               conn.Process.Path = "/USR/bin/opensnitchd"
+               if err = opList.Compile(); err != nil {
+                       t.Fail()
+               }
+               if opList.Match(conn) == false {
+                       t.Error("Test NewOperator() list.regexp.insensitive match:", conn.Process.Path)
+                       t.Fail()
+               }
+       })
+
+       restoreConnection()
+}
+
+func TestNewOperatorListsSimple(t *testing.T) {
+       t.Log("Test NewOperator() Lists simple")
+       var dummyList []Operator
+
+       opLists, err := NewOperator(Lists, false, OpDomainsLists, "testdata/lists/domains/", dummyList)
+       if err != nil {
+               t.Error("NewOperator Lists, shouldn't be nil: ", err)
+               t.Fail()
+       }
+       if err = opLists.Compile(); err != nil {
+               t.Error("NewOperator Lists, Compile() error:", err)
+       }
+       time.Sleep(time.Second)
+       t.Log("testing Lists, DstHost:", conn.DstHost)
+       // The list contains 4 lines, 1 is a comment and there's a domain duplicated.
+       // We should only load lines that start with 0.0.0.0 or 127.0.0.1
+       if len(opLists.lists) != 2 {
+               t.Error("NewOperator Lists, number of domains error:", opLists.lists, len(opLists.lists))
+       }
+       if opLists.Match(conn) == false {
+               t.Error("Test NewOperator() lists doesn't match")
+       }
+
+       opLists.StopMonitoringLists()
+       time.Sleep(time.Second)
+       opLists.Lock()
+       if len(opLists.lists) != 0 {
+               t.Error("NewOperator Lists, number should be 0 after stop:", opLists.lists, len(opLists.lists))
+       }
+       opLists.Unlock()
+
+       restoreConnection()
+}
+
+func TestNewOperatorListsIPs(t *testing.T) {
+       t.Log("Test NewOperator() Lists domains_regexp")
+
+       var subOp *Operator
+       var list []Operator
+       listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.ips", "data": "testdata/lists/ips/", "sensitive": false}]`
+
+       opLists, err := NewOperator(List, false, OpList, listData, list)
+       if err != nil {
+               t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err)
+               t.Fail()
+       }
+       if err := opLists.Compile(); err != nil {
+               t.Error("NewOperator Lists domains_regexp, Compile() error:", err)
+       }
+       opLists.List = *unmarshalListData(opLists.Data, t)
+       for i := 0; i < len(opLists.List); i++ {
+               if err := opLists.List[i].Compile(); err != nil {
+                       t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err)
+               }
+               if opLists.List[i].Type == Lists {
+                       subOp = &opLists.List[i]
+               }
+       }
+
+       time.Sleep(time.Second)
+       if opLists.Match(conn) == false {
+               t.Error("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+       }
+
+       subOp.Lock()
+       listslen := len(subOp.lists)
+       subOp.Unlock()
+       if listslen != 2 {
+               t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists)
+       }
+
+       //t.Log("checking lists.domains_regexp:", tries, conn.DstHost)
+       if opLists.Match(conn) == false {
+               // we don't care about if it matches, we're testing race conditions
+               t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+       }
+
+       subOp.StopMonitoringLists()
+       time.Sleep(time.Second)
+       subOp.Lock()
+       if len(subOp.lists) != 0 {
+               t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists))
+       }
+       subOp.Unlock()
+
+       restoreConnection()
+}
+
+func TestNewOperatorListsNETs(t *testing.T) {
+       t.Log("Test NewOperator() Lists domains_regexp")
+
+       var subOp *Operator
+       var list []Operator
+       listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.nets", "data": "testdata/lists/nets/", "sensitive": false}]`
+
+       opLists, err := NewOperator(List, false, OpList, listData, list)
+       if err != nil {
+               t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err)
+               t.Fail()
+       }
+       if err := opLists.Compile(); err != nil {
+               t.Error("NewOperator Lists domains_regexp, Compile() error:", err)
+       }
+       opLists.List = *unmarshalListData(opLists.Data, t)
+       for i := 0; i < len(opLists.List); i++ {
+               if err := opLists.List[i].Compile(); err != nil {
+                       t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err)
+               }
+               if opLists.List[i].Type == Lists {
+                       subOp = &opLists.List[i]
+               }
+       }
+
+       time.Sleep(time.Second)
+       if opLists.Match(conn) == false {
+               t.Error("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+       }
+
+       subOp.Lock()
+       listslen := len(subOp.lists)
+       subOp.Unlock()
+       if listslen != 2 {
+               t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists)
+       }
+
+       //t.Log("checking lists.domains_regexp:", tries, conn.DstHost)
+       if opLists.Match(conn) == false {
+               // we don't care about if it matches, we're testing race conditions
+               t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+       }
+
+       subOp.StopMonitoringLists()
+       time.Sleep(time.Second)
+       subOp.Lock()
+       if len(subOp.lists) != 0 {
+               t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists))
+       }
+       subOp.Unlock()
+
+       restoreConnection()
+}
+
+func TestNewOperatorListsComplex(t *testing.T) {
+       t.Log("Test NewOperator() Lists complex")
+       var subOp *Operator
+       var list []Operator
+       listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.domains", "data": "testdata/lists/domains/", "sensitive": false}]`
+
+       opLists, err := NewOperator(List, false, OpList, listData, list)
+       if err != nil {
+               t.Error("NewOperator Lists complex, shouldn't be nil: ", err)
+               t.Fail()
+       }
+       if err := opLists.Compile(); err != nil {
+               t.Error("NewOperator Lists complex, Compile() error:", err)
+       }
+       opLists.List = *unmarshalListData(opLists.Data, t)
+       for i := 0; i < len(opLists.List); i++ {
+               if err := opLists.List[i].Compile(); err != nil {
+                       t.Error("NewOperator Lists complex, Compile() subitem error:", err)
+               }
+               if opLists.List[i].Type == Lists {
+                       subOp = &opLists.List[i]
+               }
+       }
+       time.Sleep(time.Second)
+       subOp.Lock()
+       if len(subOp.lists) != 2 {
+               t.Error("NewOperator Lists complex, number of domains error:", subOp.lists)
+       }
+       subOp.Unlock()
+       if opLists.Match(conn) == false {
+               t.Error("Test NewOperator() Lists complex, doesn't match")
+       }
+
+       subOp.StopMonitoringLists()
+       time.Sleep(time.Second)
+       subOp.Lock()
+       if len(subOp.lists) != 0 {
+               t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists))
+       }
+       subOp.Unlock()
+
+       restoreConnection()
+}
+
+func TestNewOperatorListsDomainsRegexp(t *testing.T) {
+       t.Log("Test NewOperator() Lists domains_regexp")
+
+       var subOp *Operator
+       var list []Operator
+       listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.domains_regexp", "data": "testdata/lists/regexp/", "sensitive": false}]`
+
+       opLists, err := NewOperator(List, false, OpList, listData, list)
+       if err != nil {
+               t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err)
+               t.Fail()
+       }
+       if err := opLists.Compile(); err != nil {
+               t.Error("NewOperator Lists domains_regexp, Compile() error:", err)
+       }
+       opLists.List = *unmarshalListData(opLists.Data, t)
+       for i := 0; i < len(opLists.List); i++ {
+               if err := opLists.List[i].Compile(); err != nil {
+                       t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err)
+               }
+               if opLists.List[i].Type == Lists {
+                       subOp = &opLists.List[i]
+               }
+       }
+
+       time.Sleep(time.Second)
+       if opLists.Match(conn) == false {
+               t.Error("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+       }
+
+       subOp.Lock()
+       listslen := len(subOp.lists)
+       subOp.Unlock()
+       if listslen != 2 {
+               t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists)
+       }
+
+       //t.Log("checking lists.domains_regexp:", tries, conn.DstHost)
+       if opLists.Match(conn) == false {
+               // we don't care about if it matches, we're testing race conditions
+               t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+       }
+
+       subOp.StopMonitoringLists()
+       time.Sleep(time.Second)
+       subOp.Lock()
+       if len(subOp.lists) != 0 {
+               t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists))
+       }
+       subOp.Unlock()
+
+       restoreConnection()
+}
+
+// Must be launched with -race to test that we don't cause leaks
+// Race occured on operator.go:241 reListCmp().MathString()
+// fixed here: 53419fe
+func TestRaceNewOperatorListsDomainsRegexp(t *testing.T) {
+       t.Log("Test NewOperator() Lists domains_regexp")
+
+       var subOp *Operator
+       var list []Operator
+       listData := `[{"type": "simple", "operand": "user.id", "data": "666", "sensitive": false}, {"type": "lists", "operand": "lists.domains_regexp", "data": "testdata/lists/regexp/", "sensitive": false}]`
+
+       opLists, err := NewOperator(List, false, OpList, listData, list)
+       if err != nil {
+               t.Error("NewOperator Lists domains_regexp, shouldn't be nil: ", err)
+               t.Fail()
+       }
+       if err := opLists.Compile(); err != nil {
+               t.Error("NewOperator Lists domains_regexp, Compile() error:", err)
+       }
+       opLists.List = *unmarshalListData(opLists.Data, t)
+       for i := 0; i < len(opLists.List); i++ {
+               if err := opLists.List[i].Compile(); err != nil {
+                       t.Error("NewOperator Lists domains_regexp, Compile() subitem error:", err)
+               }
+               if opLists.List[i].Type == Lists {
+                       subOp = &opLists.List[i]
+               }
+       }
+
+       // touch domains list in background, to force a reload.
+       go func() {
+               touches := 1000
+               for {
+                       if touches < 0 {
+                               break
+                       }
+                       core.Exec("/bin/touch", []string{"testdata/lists/regexp/domainsregexp.txt"})
+                       touches--
+                       time.Sleep(100 * time.Millisecond)
+                       //t.Log("touching:", touches)
+               }
+       }()
+
+       time.Sleep(time.Second)
+
+       subOp.Lock()
+       listslen := len(subOp.lists)
+       subOp.Unlock()
+       if listslen != 2 {
+               t.Error("NewOperator Lists domains_regexp, number of domains error:", subOp.lists)
+       }
+
+       tries := 10000
+       for {
+               if tries < 0 {
+                       break
+               }
+               //t.Log("checking lists.domains_regexp:", tries, conn.DstHost)
+               if opLists.Match(conn) == false {
+                       // we don't care about if it matches, we're testing race conditions
+                       t.Log("Test NewOperator() Lists domains_regexp, doesn't match:", conn.DstHost)
+               }
+
+               tries--
+               time.Sleep(10 * time.Millisecond)
+       }
+
+       subOp.StopMonitoringLists()
+       time.Sleep(time.Second)
+       subOp.Lock()
+       if len(subOp.lists) != 0 {
+               t.Error("NewOperator Lists number should be 0:", subOp.lists, len(subOp.lists))
+       }
+       subOp.Unlock()
+
+       restoreConnection()
+}
diff --git a/daemon/rule/rule.go b/daemon/rule/rule.go
new file mode 100644 (file)
index 0000000..794f27c
--- /dev/null
@@ -0,0 +1,170 @@
+package rule
+
+import (
+       "fmt"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// Action of a rule
+type Action string
+
+// Actions of rules
+const (
+       Allow  = Action("allow")
+       Deny   = Action("deny")
+       Reject = Action("reject")
+)
+
+// Duration of a rule
+type Duration string
+
+// daemon possible durations
+const (
+       Once    = Duration("once")
+       Restart = Duration("until restart")
+       Always  = Duration("always")
+)
+
+// Rule represents an action on a connection.
+// The fields match the ones saved as json to disk.
+// If a .json rule file is modified on disk, it's reloaded automatically.
+type Rule struct {
+       // Save date fields as string, to avoid issues marshalling Time (#1140).
+       Created string `json:"created"`
+       Updated string `json:"updated"`
+
+       Name        string   `json:"name"`
+       Description string   `json:"description"`
+       Action      Action   `json:"action"`
+       Duration    Duration `json:"duration"`
+       Operator    Operator `json:"operator"`
+       Enabled     bool     `json:"enabled"`
+       Precedence  bool     `json:"precedence"`
+       Nolog       bool     `json:"nolog"`
+}
+
+// Create creates a new rule object with the specified parameters.
+func Create(name, description string, enabled, precedence, nolog bool, action Action, duration Duration, op *Operator) *Rule {
+       return &Rule{
+               Created:     time.Now().Format(time.RFC3339),
+               Enabled:     enabled,
+               Precedence:  precedence,
+               Nolog:       nolog,
+               Name:        name,
+               Description: description,
+               Action:      action,
+               Duration:    duration,
+               Operator:    *op,
+       }
+}
+
+func (r *Rule) String() string {
+       enabled := "Disabled"
+       if r.Enabled {
+               enabled = "Enabled"
+       }
+       return fmt.Sprintf("[%s] %s: if(%s){ %s %s }", enabled, r.Name, r.Operator.String(), r.Action, r.Duration)
+}
+
+// Match performs on a connection the checks a Rule has, to determine if it
+// must be allowed or denied.
+func (r *Rule) Match(con *conman.Connection) bool {
+       return r.Operator.Match(con)
+}
+
+// Deserialize translates back the rule received to a Rule object
+func Deserialize(reply *protocol.Rule) (*Rule, error) {
+       if reply.Operator == nil {
+               log.Warning("Deserialize rule, Operator nil")
+               return nil, fmt.Errorf("invalid operator")
+       }
+       operator, err := NewOperator(
+               Type(reply.Operator.Type),
+               Sensitive(reply.Operator.Sensitive),
+               Operand(reply.Operator.Operand),
+               reply.Operator.Data,
+               make([]Operator, 0),
+       )
+       if err != nil {
+               log.Warning("Deserialize rule, NewOperator() error: %s", err)
+               return nil, err
+       }
+
+       newRule := Create(
+               reply.Name,
+               reply.Description,
+               reply.Enabled,
+               reply.Precedence,
+               reply.Nolog,
+               Action(reply.Action),
+               Duration(reply.Duration),
+               operator,
+       )
+
+       if Type(reply.Operator.Type) == List {
+               newRule.Operator.Data = ""
+               reply.Operator.Data = ""
+               for i := 0; i < len(reply.Operator.List); i++ {
+                       newRule.Operator.List = append(
+                               newRule.Operator.List,
+                               Operator{
+                                       Type:      Type(reply.Operator.List[i].Type),
+                                       Sensitive: Sensitive(reply.Operator.List[i].Sensitive),
+                                       Operand:   Operand(reply.Operator.List[i].Operand),
+                                       Data:      string(reply.Operator.List[i].Data),
+                               },
+                       )
+               }
+       }
+
+       return newRule, nil
+}
+
+// Serialize translates a Rule to the protocol object
+func (r *Rule) Serialize() *protocol.Rule {
+       if r == nil {
+               return nil
+       }
+
+       created, err := time.Parse(time.RFC3339, r.Created)
+       if err != nil {
+               log.Warning("Error parsing rule Created date (it should be in RFC3339 format): %s  (%s)", err, string(r.Name))
+               log.Warning("using current time instead: %s", created)
+               created = time.Now()
+       }
+
+       protoRule := &protocol.Rule{
+               Created:     created.Unix(),
+               Name:        string(r.Name),
+               Description: string(r.Description),
+               Enabled:     bool(r.Enabled),
+               Precedence:  bool(r.Precedence),
+               Nolog:       bool(r.Nolog),
+               Action:      string(r.Action),
+               Duration:    string(r.Duration),
+               Operator: &protocol.Operator{
+                       Type:      string(r.Operator.Type),
+                       Sensitive: bool(r.Operator.Sensitive),
+                       Operand:   string(r.Operator.Operand),
+                       Data:      string(r.Operator.Data),
+               },
+       }
+       if r.Operator.Type == List {
+               r.Operator.Data = ""
+               for i := 0; i < len(r.Operator.List); i++ {
+                       protoRule.Operator.List = append(protoRule.Operator.List,
+                               &protocol.Operator{
+                                       Type:      string(r.Operator.List[i].Type),
+                                       Sensitive: bool(r.Operator.List[i].Sensitive),
+                                       Operand:   string(r.Operator.List[i].Operand),
+                                       Data:      string(r.Operator.List[i].Data),
+                               })
+               }
+       }
+
+       return protoRule
+}
diff --git a/daemon/rule/rule_test.go b/daemon/rule/rule_test.go
new file mode 100644 (file)
index 0000000..bde6f19
--- /dev/null
@@ -0,0 +1,119 @@
+package rule
+
+import (
+       "testing"
+)
+
+func TestCreate(t *testing.T) {
+       t.Log("Test: Create rule")
+
+       var list []Operator
+       oper, _ := NewOperator(Simple, false, OpTrue, "", list)
+       r := Create("000-test-name", "rule description 000", true, false, false, Allow, Once, oper)
+       t.Run("New rule must not be nil", func(t *testing.T) {
+               if r == nil {
+                       t.Error("Create() returned nil")
+                       t.Fail()
+               }
+       })
+       t.Run("Rule name must be 000-test-name", func(t *testing.T) {
+               if r.Name != "000-test-name" {
+                       t.Error("Rule name error:", r.Name)
+                       t.Fail()
+               }
+       })
+       t.Run("Rule must be enabled", func(t *testing.T) {
+               if r.Enabled == false {
+                       t.Error("Rule Enabled is false:", r)
+                       t.Fail()
+               }
+       })
+       t.Run("Rule Precedence must be false", func(t *testing.T) {
+               if r.Precedence == true {
+                       t.Error("Rule Precedence is true:", r)
+                       t.Fail()
+               }
+       })
+       t.Run("Rule Action must be Allow", func(t *testing.T) {
+               if r.Action != Allow {
+                       t.Error("Rule Action is not Allow:", r.Action)
+                       t.Fail()
+               }
+       })
+       t.Run("Rule Duration should be Once", func(t *testing.T) {
+               if r.Duration != Once {
+                       t.Error("Rule Duration is not Once:", r.Duration)
+                       t.Fail()
+               }
+       })
+}
+
+func TestRuleSerializers(t *testing.T) {
+       t.Log("Test: Serializers()")
+
+       var opList []Operator
+       opList = append(opList, Operator{
+               Type:    Simple,
+               Operand: OpProcessPath,
+               Data:    "/path/x",
+       })
+       opList = append(opList, Operator{
+               Type:    Simple,
+               Operand: OpDstPort,
+               Data:    "23",
+       })
+
+       op, _ := NewOperator(List, false, OpTrue, "", opList)
+       // this string must be erased after Deserialized
+       op.Data = "[\"test\": true]"
+
+       r := Create("000-test-serializer-list", "rule description 000", true, false, false, Allow, Once, op)
+
+       rSerialized := r.Serialize()
+       t.Run("Serialize() must not return nil", func(t *testing.T) {
+               if rSerialized == nil {
+                       t.Error("rule.Serialize() returned nil")
+                       t.Fail()
+               }
+       })
+
+       rDeser, err := Deserialize(rSerialized)
+       t.Run("Deserialize must not return error", func(t *testing.T) {
+               if err != nil {
+                       t.Error("rule.Serialize() returned error:", err)
+                       t.Fail()
+               }
+       })
+
+       // commit: b93051026e6a82ba07a5ac2f072880e69f04c238
+       t.Run("Deserialize. Operator.Data must be empty", func(t *testing.T) {
+               if rDeser.Operator.Data != "" {
+                       t.Error("rule.Deserialize() Operator.Data not emptied:", rDeser.Operator.Data)
+                       t.Fail()
+               }
+       })
+
+       t.Run("Deserialize. Operator.List must be expanded", func(t *testing.T) {
+               if len(rDeser.Operator.List) != 2 {
+                       t.Error("rule.Deserialize() invalid Operator.List:", rDeser.Operator.List)
+                       t.Fail()
+               }
+               if rDeser.Operator.List[0].Operand != OpProcessPath {
+                       t.Error("rule.Deserialize() invalid Operator.List 1:", rDeser.Operator.List)
+                       t.Fail()
+               }
+               if rDeser.Operator.List[1].Operand != OpDstPort {
+                       t.Error("rule.Deserialize() invalid Operator.List 2:", rDeser.Operator.List)
+                       t.Fail()
+               }
+               if rDeser.Operator.List[0].Type != Simple || rDeser.Operator.List[1].Type != Simple {
+                       t.Error("rule.Deserialize() invalid Operator.List 3:", rDeser.Operator.List)
+                       t.Fail()
+               }
+               if rDeser.Operator.List[0].Data != "/path/x" || rDeser.Operator.List[1].Data != "23" {
+                       t.Error("rule.Deserialize() invalid Operator.List 4:", rDeser.Operator.List)
+                       t.Fail()
+               }
+       })
+
+}
diff --git a/daemon/rule/testdata/000-allow-chrome.json b/daemon/rule/testdata/000-allow-chrome.json
new file mode 100644 (file)
index 0000000..db2c811
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "created": "2020-12-13T18:06:52.209804547+01:00",
+  "updated": "2020-12-13T18:06:52.209857713+01:00",
+  "name": "000-allow-chrome",
+  "enabled": true,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "simple",
+    "operand": "process.path",
+    "sensitive": false,
+    "data": "/opt/google/chrome/chrome",
+    "list": []
+  }
+}
\ No newline at end of file
diff --git a/daemon/rule/testdata/001-deny-chrome.json b/daemon/rule/testdata/001-deny-chrome.json
new file mode 100644 (file)
index 0000000..27c266c
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "created": "2020-12-13T17:54:49.067148304+01:00",
+  "updated": "2020-12-13T17:54:49.067213602+01:00",
+  "name": "001-deny-chrome",
+  "enabled": true,
+  "precedence": false,
+  "action": "deny",
+  "duration": "always",
+  "operator": {
+    "type": "simple",
+    "operand": "process.path",
+    "sensitive": false,
+    "data": "/opt/google/chrome/chrome",
+    "list": []
+  }
+}
\ No newline at end of file
diff --git a/daemon/rule/testdata/invalid-regexp-list.json b/daemon/rule/testdata/invalid-regexp-list.json
new file mode 100644 (file)
index 0000000..bd8973f
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "created": "2020-12-13T18:06:52.209804547+01:00",
+  "updated": "2020-12-13T18:06:52.209857713+01:00",
+  "name": "invalid-regexp-list",
+  "enabled": true,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "list",
+    "operand": "list",
+    "sensitive": false,
+    "data": "[{\"type\": \"regexp\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"^(/di(rmngr$\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]",
+    "list": [
+        {
+        "type": "regexp",
+        "operand": "process.path",
+        "sensitive": false,
+        "data": "^(/di(rmngr)$",
+        "list": null
+      },
+      {
+        "type": "simple",
+        "operand": "dest.port",
+        "sensitive": false,
+        "data": "53",
+        "list": null
+      }
+    ]
+  }
+}
diff --git a/daemon/rule/testdata/invalid-regexp.json b/daemon/rule/testdata/invalid-regexp.json
new file mode 100644 (file)
index 0000000..d296098
--- /dev/null
@@ -0,0 +1,16 @@
+{
+  "created": "2020-12-13T18:06:52.209804547+01:00",
+  "updated": "2020-12-13T18:06:52.209857713+01:00",
+  "name": "invalid-regexp",
+  "enabled": true,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "regexp",
+    "operand": "process.path",
+    "sensitive": false,
+    "data": "/opt/((.*)google/chrome/chrome",
+    "list": []
+  }
+}
diff --git a/daemon/rule/testdata/lists/domains/domainlists.txt b/daemon/rule/testdata/lists/domains/domainlists.txt
new file mode 100644 (file)
index 0000000..6e2f3e2
--- /dev/null
@@ -0,0 +1,4 @@
+# this line must be ignored, 0.0.0.0 www.test.org
+0.0.0.0 www.test.org
+127.0.0.1 www.test.org
+0.0.0.0 opensnitch.io
diff --git a/daemon/rule/testdata/lists/ips/ips.txt b/daemon/rule/testdata/lists/ips/ips.txt
new file mode 100644 (file)
index 0000000..6514d30
--- /dev/null
@@ -0,0 +1,7 @@
+# this line must be ignored, 0.0.0.0 www.test.org
+
+# empty lines are also ignored
+1.1.1.1
+185.53.178.14
+# duplicated entries should be ignored
+1.1.1.1
diff --git a/daemon/rule/testdata/lists/nets/nets.txt b/daemon/rule/testdata/lists/nets/nets.txt
new file mode 100644 (file)
index 0000000..8041c92
--- /dev/null
@@ -0,0 +1,8 @@
+# this line must be ignored, 0.0.0.0 www.test.org
+
+# empty lines are also ignored
+1.1.1.0/24
+185.53.178.0/24
+# duplicated entries should be ignored
+1.1.1.0/24
+
diff --git a/daemon/rule/testdata/lists/regexp/domainsregexp.txt b/daemon/rule/testdata/lists/regexp/domainsregexp.txt
new file mode 100644 (file)
index 0000000..85ab3e9
--- /dev/null
@@ -0,0 +1,4 @@
+# this line must be ignored, 0.0.0.0 www.test.org
+www.test.org
+www.test.org
+opensnitch.io
diff --git a/daemon/rule/testdata/live_reload/test-live-reload-delete.json b/daemon/rule/testdata/live_reload/test-live-reload-delete.json
new file mode 100644 (file)
index 0000000..5a4591a
--- /dev/null
@@ -0,0 +1,16 @@
+{
+    "created": "2020-12-13T18:06:52.209804547+01:00",
+    "updated": "2020-12-13T18:06:52.209857713+01:00",
+    "name": "test-live-reload-delete",
+    "enabled": true,
+    "precedence": true,
+    "action": "deny",
+    "duration": "always",
+    "operator": {
+      "type": "simple",
+      "operand": "process.path",
+      "sensitive": false,
+      "data": "/usr/bin/curl",
+      "list": []
+    }
+  }
\ No newline at end of file
diff --git a/daemon/rule/testdata/live_reload/test-live-reload-remove.json b/daemon/rule/testdata/live_reload/test-live-reload-remove.json
new file mode 100644 (file)
index 0000000..8f21ed9
--- /dev/null
@@ -0,0 +1,16 @@
+{
+    "created": "2020-12-13T18:06:52.209804547+01:00",
+    "updated": "2020-12-13T18:06:52.209857713+01:00",
+    "name": "test-live-reload-remove",
+    "enabled": true,
+    "precedence": true,
+    "action": "deny",
+    "duration": "always",
+    "operator": {
+      "type": "simple",
+      "operand": "process.path",
+      "sensitive": false,
+      "data": "/usr/bin/curl",
+      "list": []
+    }
+  }
\ No newline at end of file
diff --git a/daemon/rule/testdata/rule-disabled-operator-list-expanded.json b/daemon/rule/testdata/rule-disabled-operator-list-expanded.json
new file mode 100644 (file)
index 0000000..2d6153f
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "created": "2023-10-03T18:06:52.209804547+01:00",
+  "updated": "2023-10-03T18:06:52.209857713+01:00",
+  "name": "rule-disabled-with-operators-list-expanded",
+  "enabled": false,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "list",
+    "operand": "list",
+    "sensitive": false,
+    "data": "",
+    "list": [
+        {
+        "type": "simple",
+        "operand": "process.path",
+        "sensitive": false,
+        "data": "/usr/bin/telnet",
+        "list": null
+      },
+      {
+        "type": "simple",
+        "operand": "dest.port",
+        "sensitive": false,
+        "data": "53",
+        "list": null
+      }
+    ]
+  }
+}
diff --git a/daemon/rule/testdata/rule-disabled-operator-list.json b/daemon/rule/testdata/rule-disabled-operator-list.json
new file mode 100644 (file)
index 0000000..29c8a7a
--- /dev/null
@@ -0,0 +1,17 @@
+{
+  "created": "2023-10-03T18:06:52.209804547+01:00",
+  "updated": "2023-10-03T18:06:52.209857713+01:00",
+  "name": "rule-disabled-with-operators-list-as-json-string",
+  "enabled": false,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "list",
+    "operand": "list",
+    "sensitive": false,
+    "data": "[{\"type\": \"simple\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"/usr/bin/telnet\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]",
+    "list": [
+    ]
+  }
+}
diff --git a/daemon/rule/testdata/rule-operator-list-data-empty.json b/daemon/rule/testdata/rule-operator-list-data-empty.json
new file mode 100644 (file)
index 0000000..76218c6
--- /dev/null
@@ -0,0 +1,32 @@
+{
+  "created": "2023-10-03T18:06:52.209804547+01:00",
+  "updated": "2023-10-03T18:06:52.209857713+01:00",
+  "name": "rule-with-operator-list-data-empty",
+  "enabled": true,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "list",
+    "operand": "list",
+    "sensitive": false,
+    "data": "",
+    "list": [
+        {
+        "type": "simple",
+        "operand": "process.path",
+        "sensitive": false,
+        "data": "/usr/bin/telnet",
+        "list": null
+      },
+      {
+        "type": "simple",
+        "operand": "dest.port",
+        "sensitive": false,
+        "data": "53",
+        "list": null
+      }
+    ]
+  }
+}
+
diff --git a/daemon/rule/testdata/rule-operator-list.json b/daemon/rule/testdata/rule-operator-list.json
new file mode 100644 (file)
index 0000000..f4c46d8
--- /dev/null
@@ -0,0 +1,31 @@
+{
+  "created": "2023-10-03T18:06:52.209804547+01:00",
+  "updated": "2023-10-03T18:06:52.209857713+01:00",
+  "name": "rule-with-operator-list",
+  "enabled": true,
+  "precedence": true,
+  "action": "allow",
+  "duration": "always",
+  "operator": {
+    "type": "list",
+    "operand": "list",
+    "sensitive": false,
+    "data": "[{\"type\": \"simple\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"/usr/bin/telnet\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]",
+    "list": [
+        {
+        "type": "simple",
+        "operand": "process.path",
+        "sensitive": false,
+        "data": "/usr/bin/telnet",
+        "list": null
+      },
+      {
+        "type": "simple",
+        "operand": "dest.port",
+        "sensitive": false,
+        "data": "53",
+        "list": null
+      }
+    ]
+  }
+}
diff --git a/daemon/statistics/event.go b/daemon/statistics/event.go
new file mode 100644 (file)
index 0000000..fe9e9ee
--- /dev/null
@@ -0,0 +1,32 @@
+package statistics
+
+import (
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+type Event struct {
+       Time       time.Time
+       Connection *conman.Connection
+       Rule       *rule.Rule
+}
+
+func NewEvent(con *conman.Connection, match *rule.Rule) *Event {
+       return &Event{
+               Time:       time.Now(),
+               Connection: con,
+               Rule:       match,
+       }
+}
+
+func (e *Event) Serialize() *protocol.Event {
+       return &protocol.Event{
+               Time:       e.Time.Format("2006-01-02 15:04:05"),
+               Connection: e.Connection.Serialize(),
+               Rule:       e.Rule.Serialize(),
+               Unixnano:   e.Time.UnixNano(),
+       }
+}
diff --git a/daemon/statistics/stats.go b/daemon/statistics/stats.go
new file mode 100644 (file)
index 0000000..68fd5de
--- /dev/null
@@ -0,0 +1,263 @@
+package statistics
+
+import (
+       "strconv"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/loggers"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+)
+
+// StatsConfig holds the stats confguration
+type StatsConfig struct {
+       MaxEvents int `json:"MaxEvents"`
+       MaxStats  int `json:"MaxStats"`
+       Workers   int `json:"Workers"`
+}
+
+type conEvent struct {
+       con       *conman.Connection
+       match     *rule.Rule
+       wasMissed bool
+}
+
+// Statistics holds the connections and statistics the daemon intercepts.
+// The connections are stored in the Events slice.
+type Statistics struct {
+       logger       *loggers.LoggerManager
+       rules        *rule.Loader
+       Started      time.Time
+       ByExecutable map[string]uint64
+       ByPort       map[string]uint64
+       ByProto      map[string]uint64
+       ByAddress    map[string]uint64
+       ByHost       map[string]uint64
+       jobs         chan conEvent
+       ByUID        map[string]uint64
+       Events       []*Event
+       Dropped      int
+       // max number of events to keep in the buffer
+       maxEvents int
+       // max number of entries for each By* map
+       maxStats     int
+       DNSResponses int
+       Connections  int
+       Ignored      int
+       Accepted     int
+       RuleHits     int
+       RuleMisses   int
+
+       sync.RWMutex
+}
+
+// New returns a new Statistics object and initializes the go routines to update the stats.
+func New(rules *rule.Loader) (stats *Statistics) {
+       stats = &Statistics{
+               Started:      time.Now(),
+               Events:       make([]*Event, 0),
+               ByProto:      make(map[string]uint64),
+               ByAddress:    make(map[string]uint64),
+               ByHost:       make(map[string]uint64),
+               ByPort:       make(map[string]uint64),
+               ByUID:        make(map[string]uint64),
+               ByExecutable: make(map[string]uint64),
+
+               rules:     rules,
+               jobs:      make(chan conEvent),
+               maxEvents: 150,
+               maxStats:  25,
+       }
+
+       return stats
+}
+
+// SetLoggers sets the configured loggers where we'll write the events.
+func (s *Statistics) SetLoggers(loggers *loggers.LoggerManager) {
+       s.logger = loggers
+}
+
+// SetLimits configures the max events to keep in the backlog before sending
+// the stats to the UI, or while the UI is not connected.
+// if the backlog is full, it'll be shifted by one.
+func (s *Statistics) SetLimits(config StatsConfig) {
+       if config.MaxEvents > 0 {
+               s.maxEvents = config.MaxEvents
+       }
+       if config.MaxStats > 0 {
+               s.maxStats = config.MaxStats
+       }
+       wrks := config.Workers
+       if wrks == 0 {
+               wrks = 6
+       }
+       log.Info("Stats, max events: %d, max stats: %d, max workers: %d", s.maxStats, s.maxEvents, wrks)
+       for i := 0; i < wrks; i++ {
+               go s.eventWorker(i)
+       }
+
+}
+
+// OnConnectionEvent sends the details of a new connection throughout a channel,
+// in order to add the connection to the stats.
+func (s *Statistics) OnConnectionEvent(con *conman.Connection, match *rule.Rule, wasMissed bool) {
+       s.jobs <- conEvent{
+               con:       con,
+               match:     match,
+               wasMissed: wasMissed,
+       }
+       action := "<nil>"
+       rname := "<nil>"
+       if match != nil {
+               action = string(match.Action)
+               rname = string(match.Name)
+       }
+
+       s.logger.Log(con.Serialize(), action, rname)
+}
+
+// OnDNSResponse increases the counter of dns and accepted connections.
+func (s *Statistics) OnDNSResponse() {
+       s.Lock()
+       defer s.Unlock()
+       s.DNSResponses++
+       s.Accepted++
+}
+
+// OnIgnored increases the counter of ignored and accepted connections.
+func (s *Statistics) OnIgnored() {
+       s.Lock()
+       defer s.Unlock()
+       s.Ignored++
+       s.Accepted++
+}
+
+func (s *Statistics) incMap(m *map[string]uint64, key string) {
+       if val, found := (*m)[key]; found == false {
+               // do we have enough space left?
+               nElems := len(*m)
+               if nElems >= s.maxStats {
+                       // find the element with less hits
+                       nMin := uint64(9999999999)
+                       minKey := ""
+                       for k, v := range *m {
+                               if v < nMin {
+                                       minKey = k
+                                       nMin = v
+                               }
+                       }
+                       // remove it
+                       if minKey != "" {
+                               delete(*m, minKey)
+                       }
+               }
+
+               (*m)[key] = 1
+       } else {
+               (*m)[key] = val + 1
+       }
+}
+
+func (s *Statistics) eventWorker(id int) {
+       log.Debug("Stats worker #%d started.", id)
+
+       for true {
+               select {
+               case job := <-s.jobs:
+                       s.onConnection(job.con, job.match, job.wasMissed)
+               }
+       }
+}
+
+func (s *Statistics) onConnection(con *conman.Connection, match *rule.Rule, wasMissed bool) {
+       s.Lock()
+       defer s.Unlock()
+
+       s.Connections++
+
+       if wasMissed {
+               s.RuleMisses++
+       } else {
+               s.RuleHits++
+       }
+
+       if wasMissed == false && match.Action == rule.Allow {
+               s.Accepted++
+       } else {
+               s.Dropped++
+       }
+
+       s.incMap(&s.ByProto, con.Protocol)
+       s.incMap(&s.ByAddress, con.DstIP.String())
+       if con.DstHost != "" {
+               s.incMap(&s.ByHost, con.DstHost)
+       }
+       s.incMap(&s.ByPort, strconv.FormatUint(uint64(con.DstPort), 10))
+       s.incMap(&s.ByUID, strconv.Itoa(con.Entry.UserId))
+       s.incMap(&s.ByExecutable, con.Process.Path)
+
+       // if we reached the limit, shift everything back
+       // by one position
+       nEvents := len(s.Events)
+       if nEvents == s.maxEvents {
+               s.Events = s.Events[1:]
+       }
+       if wasMissed {
+               return
+       }
+       s.Events = append(s.Events, NewEvent(con, match))
+}
+
+func (s *Statistics) serializeEvents() []*protocol.Event {
+       nEvents := len(s.Events)
+       serialized := make([]*protocol.Event, nEvents)
+
+       for i, e := range s.Events {
+               serialized[i] = e.Serialize()
+       }
+
+       return serialized
+}
+
+// emptyStats empties the stats once we've sent them to the GUI.
+// We don't need them anymore here.
+func (s *Statistics) emptyStats() {
+       s.Lock()
+       if len(s.Events) > 0 {
+               s.Events = make([]*Event, 0)
+       }
+       s.Unlock()
+}
+
+// Serialize returns the collected statistics.
+// After return the stats, the Events are emptied, to keep collecting more stats
+// and not miss connections.
+func (s *Statistics) Serialize() *protocol.Statistics {
+       s.Lock()
+       defer s.emptyStats()
+       defer s.Unlock()
+
+       return &protocol.Statistics{
+               DaemonVersion: core.Version,
+               Rules:         uint64(s.rules.NumRules()),
+               Uptime:        uint64(time.Since(s.Started).Seconds()),
+               DnsResponses:  uint64(s.DNSResponses),
+               Connections:   uint64(s.Connections),
+               Ignored:       uint64(s.Ignored),
+               Accepted:      uint64(s.Accepted),
+               Dropped:       uint64(s.Dropped),
+               RuleHits:      uint64(s.RuleHits),
+               RuleMisses:    uint64(s.RuleMisses),
+               Events:        s.serializeEvents(),
+               ByProto:       s.ByProto,
+               ByAddress:     s.ByAddress,
+               ByHost:        s.ByHost,
+               ByPort:        s.ByPort,
+               ByUid:         s.ByUID,
+               ByExecutable:  s.ByExecutable,
+       }
+}
diff --git a/daemon/system-fw.json b/daemon/system-fw.json
new file mode 100644 (file)
index 0000000..1c1368b
--- /dev/null
@@ -0,0 +1,255 @@
+{
+  "Enabled": true,
+  "Version": 1,
+  "SystemRules": [
+    {
+      "Rule": {
+        "Table": "mangle",
+        "Chain": "OUTPUT",
+        "Enabled": false,
+        "Position": "0",
+        "Description": "Allow icmp",
+        "Parameters": "-p icmp",
+        "Expressions": [],
+        "Target": "ACCEPT",
+        "TargetParameters": ""
+      },
+      "Chains": []
+    },
+    {
+      "Chains": [
+        {
+          "Name": "forward",
+          "Table": "filter",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "filter",
+          "Hook": "forward",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "output",
+          "Table": "filter",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "filter",
+          "Hook": "output",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "input",
+          "Table": "filter",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "filter",
+          "Hook": "input",
+          "Policy": "accept",
+          "Rules": [
+            {
+              "Enabled": false,
+              "Position": "0",
+              "Description": "Allow SSH server connections when input policy is DROP",
+              "Parameters": "",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "tcp",
+                    "Values": [
+                      {
+                        "Key": "dport",
+                        "Value": "22"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            } 
+          ]
+        },
+        {
+          "Name": "filter-prerouting",
+          "Table": "nat",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "filter",
+          "Hook": "prerouting",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "prerouting",
+          "Table": "mangle",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "mangle",
+          "Hook": "prerouting",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "postrouting",
+          "Table": "mangle",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "mangle",
+          "Hook": "postrouting",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "prerouting",
+          "Table": "nat",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "natdest",
+          "Hook": "prerouting",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "postrouting",
+          "Table": "nat",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "natsource",
+          "Hook": "postrouting",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "input",
+          "Table": "nat",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "natsource",
+          "Hook": "input",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "output",
+          "Table": "nat",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "natdest",
+          "Hook": "output",
+          "Policy": "accept",
+          "Rules": []
+        },
+        {
+          "Name": "output",
+          "Table": "mangle",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "mangle",
+          "Hook": "output",
+          "Policy": "accept",
+          "Rules": [
+            {
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Allow ICMP",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "icmp",
+                    "Values": [
+                      {
+                        "Key": "type",
+                        "Value": "echo-request,echo-reply,destination-unreachable"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            },
+            {
+              "Enabled": true,
+              "Position": "0",
+              "Description": "Allow ICMPv6",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "icmpv6",
+                    "Values": [
+                      {
+                        "Key": "type",
+                        "Value": "echo-request,echo-reply,destination-unreachable"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            },
+            {
+              "Enabled": false,
+              "Position": "0",
+              "Description": "Exclude WireGuard VPN from being intercepted",
+              "Parameters": "",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "udp",
+                    "Values": [
+                      {
+                        "Key": "dport",
+                        "Value": "51820"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "accept",
+              "TargetParameters": ""
+            }
+          ]
+        },
+        {
+          "Name": "forward",
+          "Table": "mangle",
+          "Family": "inet",
+          "Priority": "",
+          "Type": "mangle",
+          "Hook": "forward",
+          "Policy": "accept",
+          "Rules": [
+            {
+              "UUID": "7d7394e1-100d-4b87-a90a-cd68c46edb0b",
+              "Enabled": false,
+              "Position": "0",
+              "Description": "Intercept forwarded connections (docker, etc)",
+              "Expressions": [
+                {
+                  "Statement": {
+                    "Op": "",
+                    "Name": "ct",
+                    "Values": [
+                      {
+                        "Key": "state",
+                        "Value": "new"
+                      }
+                    ]
+                  }
+                }
+              ],
+              "Target": "queue",
+              "TargetParameters": "num 0"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/daemon/ui/alerts.go b/daemon/ui/alerts.go
new file mode 100644 (file)
index 0000000..2bb8ae9
--- /dev/null
@@ -0,0 +1,125 @@
+package ui
+
+import (
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+       "golang.org/x/net/context"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/encoding/gzip"
+)
+
+// NewWarningAlert builts a new warning alert
+func NewWarningAlert(what protocol.Alert_What, data interface{}) *protocol.Alert {
+       return NewAlert(protocol.Alert_WARNING, what, protocol.Alert_SHOW_ALERT, protocol.Alert_MEDIUM, data)
+}
+
+// NewErrorAlert builts a new error alert
+func NewErrorAlert(what protocol.Alert_What, data interface{}) *protocol.Alert {
+       return NewAlert(protocol.Alert_ERROR, what, protocol.Alert_SHOW_ALERT, protocol.Alert_HIGH, data)
+}
+
+// NewAlert builts a new generic alert
+func NewAlert(atype protocol.Alert_Type, what protocol.Alert_What, action protocol.Alert_Action, prio protocol.Alert_Priority, data interface{}) *protocol.Alert {
+       a := &protocol.Alert{
+               Id:       uint64(time.Now().UnixNano()),
+               Type:     atype,
+               Action:   action,
+               What:     what,
+               Priority: prio,
+       }
+
+       switch what {
+       case protocol.Alert_KERNEL_EVENT:
+
+               switch data.(type) {
+               case procmon.Process:
+                       a.Data = &protocol.Alert_Proc{
+                               data.(*procmon.Process).Serialize(),
+                       }
+               case string:
+                       a.Data = &protocol.Alert_Text{data.(string)}
+                       a.Action = protocol.Alert_SHOW_ALERT
+               }
+       case protocol.Alert_CONNECTION:
+               a.Data = &protocol.Alert_Conn{
+                       data.(*conman.Connection).Serialize(),
+               }
+       case protocol.Alert_GENERIC:
+               a.Data = &protocol.Alert_Text{data.(string)}
+       }
+
+       return a
+}
+
+// SendInfoAlert sends an info alert
+func (c *Client) SendInfoAlert(data interface{}) {
+       c.PostAlert(protocol.Alert_INFO, protocol.Alert_GENERIC, protocol.Alert_SHOW_ALERT, protocol.Alert_LOW, data)
+}
+
+// SendWarningAlert sends an warning alert
+func (c *Client) SendWarningAlert(data interface{}) {
+       c.PostAlert(protocol.Alert_WARNING, protocol.Alert_GENERIC, protocol.Alert_SHOW_ALERT, protocol.Alert_MEDIUM, data)
+}
+
+// SendErrorAlert sends an error alert
+func (c *Client) SendErrorAlert(data interface{}) {
+       c.PostAlert(protocol.Alert_ERROR, protocol.Alert_GENERIC, protocol.Alert_SHOW_ALERT, protocol.Alert_HIGH, data)
+}
+
+// alertsDispatcher waits to be connected to the GUI.
+// Once connected, dispatches all the queued alerts.
+func (c *Client) alertsDispatcher() {
+       queuedAlerts := make(chan protocol.Alert, 32)
+       connected := false
+
+       isQueueFull := func(qdAlerts chan protocol.Alert) bool { return len(qdAlerts) > 31 }
+       isQueueEmpty := func(qdAlerts chan protocol.Alert) bool { return len(qdAlerts) == 0 }
+       queueAlert := func(qdAlerts chan protocol.Alert, pbAlert protocol.Alert) {
+               if isQueueFull(qdAlerts) {
+                       v := <-qdAlerts
+                       // empty queue before adding a new one
+                       log.Debug("Discarding queued alert (%d): %v", len(qdAlerts), v)
+               }
+               select {
+               case qdAlerts <- pbAlert:
+               default:
+                       log.Debug("Alert not sent to queue, full? (%d)", len(qdAlerts))
+               }
+       }
+
+       for {
+               select {
+               case pbAlert := <-c.alertsChan:
+                       if !connected {
+                               queueAlert(queuedAlerts, pbAlert)
+                               continue
+                       }
+                       c.dispatchAlert(pbAlert)
+               case ready := <-c.isConnected:
+                       connected = ready
+                       if ready {
+                               log.Important("UI connected, dispathing queued alerts: %d", len(c.alertsChan))
+                               for {
+                                       if isQueueEmpty(queuedAlerts) {
+                                               // no more queued alerts, exit
+                                               break
+                                       }
+                                       c.dispatchAlert(<-queuedAlerts)
+                               }
+                       }
+               }
+       }
+}
+
+func (c *Client) dispatchAlert(pbAlert protocol.Alert) {
+       if c.client == nil {
+               return
+       }
+       ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+       c.client.PostAlert(ctx, &pbAlert, grpc.UseCompressor(gzip.Name))
+       cancel()
+}
diff --git a/daemon/ui/auth/auth.go b/daemon/ui/auth/auth.go
new file mode 100644 (file)
index 0000000..b02a7e8
--- /dev/null
@@ -0,0 +1,102 @@
+package auth
+
+import (
+       "crypto/tls"
+       "crypto/x509"
+       "io/ioutil"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/ui/config"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/credentials"
+)
+
+// client auth types:
+// https://pkg.go.dev/crypto/tls#ClientAuthType
+var (
+       clientAuthType = map[string]tls.ClientAuthType{
+               "no-client-cert":      tls.NoClientCert,
+               "req-cert":            tls.RequestClientCert,
+               "req-any-cert":        tls.RequireAnyClientCert,
+               "verify-cert":         tls.VerifyClientCertIfGiven,
+               "req-and-verify-cert": tls.RequireAndVerifyClientCert,
+       }
+)
+
+const (
+       // AuthSimple will use WithInsecure()
+       AuthSimple = "simple"
+
+       // AuthTLSSimple will use a common CA certificate, shared between the server
+       // and all the clients.
+       AuthTLSSimple = "tls-simple"
+
+       // AuthTLSMutual will use a CA certificate and a client cert and key
+       // to authenticate each client.
+       AuthTLSMutual = "tls-mutual"
+)
+
+// New returns the configuration that the UI will use
+// to connect with the server.
+func New(config *config.Config) (grpc.DialOption, error) {
+       config.RLock()
+
+       credsType := config.Server.Authentication.Type
+       tlsOpts := config.Server.Authentication.TLSOptions
+
+       config.RUnlock()
+
+       if credsType == "" || credsType == AuthSimple {
+               log.Debug("UI auth: simple")
+               return grpc.WithInsecure(), nil
+       }
+       certPool := x509.NewCertPool()
+
+       // use CA certificate to authenticate clients if supplied
+       if tlsOpts.CACert != "" {
+               if caPem, err := ioutil.ReadFile(tlsOpts.CACert); err != nil {
+                       log.Warning("reading UI auth CA certificate (%s): %s", credsType, err)
+               } else {
+                       if !certPool.AppendCertsFromPEM(caPem) {
+                               log.Warning("adding UI auth CA certificate (%s): %s", credsType, err)
+                       }
+               }
+       }
+
+       // use server certificate to authenticate clients if supplied
+       if tlsOpts.ServerCert != "" {
+               if serverPem, err := ioutil.ReadFile(tlsOpts.ServerCert); err != nil {
+                       log.Warning("reading auth server cert: %s", err)
+               } else {
+                       if !certPool.AppendCertsFromPEM(serverPem) {
+                               log.Warning("adding UI auth server cert (%s): %s", credsType, err)
+                       }
+               }
+       }
+
+       // set config of tls credential
+       // https://pkg.go.dev/crypto/tls#Config
+       tlsCfg := &tls.Config{
+               InsecureSkipVerify: tlsOpts.SkipVerify,
+               RootCAs:            certPool,
+       }
+
+       // https://pkg.go.dev/google.golang.org/grpc/credentials#SecurityLevel
+       if credsType == AuthTLSMutual {
+               tlsCfg.ClientAuth = clientAuthType[tlsOpts.ClientAuthType]
+               clientCert, err := tls.LoadX509KeyPair(
+                       tlsOpts.ClientCert,
+                       tlsOpts.ClientKey,
+               )
+               if err != nil {
+                       return nil, err
+               }
+               log.Debug("   using client cert: %s", tlsOpts.ClientCert)
+               log.Debug("   using client key: %s", tlsOpts.ClientKey)
+               tlsCfg.Certificates = []tls.Certificate{clientCert}
+       }
+
+       return grpc.WithTransportCredentials(
+               credentials.NewTLS(tlsCfg),
+       ), nil
+}
diff --git a/daemon/ui/client.go b/daemon/ui/client.go
new file mode 100644 (file)
index 0000000..be013d2
--- /dev/null
@@ -0,0 +1,368 @@
+package ui
+
+import (
+       "fmt"
+       "net"
+       "sync"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/conman"
+       "github.com/evilsocket/opensnitch/daemon/firewall/iptables"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/loggers"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/statistics"
+       "github.com/evilsocket/opensnitch/daemon/ui/auth"
+       "github.com/evilsocket/opensnitch/daemon/ui/config"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+
+       "github.com/fsnotify/fsnotify"
+       "golang.org/x/net/context"
+       "google.golang.org/grpc"
+       "google.golang.org/grpc/connectivity"
+       "google.golang.org/grpc/keepalive"
+)
+
+var (
+       configFile             = "/etc/opensnitchd/default-config.json"
+       dummyOperator, _       = rule.NewOperator(rule.Simple, false, rule.OpTrue, "", make([]rule.Operator, 0))
+       clientDisconnectedRule = rule.Create("ui.client.disconnected", "", true, false, false, rule.Allow, rule.Once, dummyOperator)
+       // While the GUI is connected, deny by default everything until the user takes an action.
+       clientConnectedRule = rule.Create("ui.client.connected", "", true, false, false, rule.Deny, rule.Once, dummyOperator)
+       clientErrorRule     = rule.Create("ui.client.error", "", true, false, false, rule.Allow, rule.Once, dummyOperator)
+       clientConfig        config.Config
+
+       maxQueuedAlerts = 1024
+)
+
+// Client holds the connection information of a client.
+type Client struct {
+       rules               *rule.Loader
+       stats               *statistics.Statistics
+       con                 *grpc.ClientConn
+       configWatcher       *fsnotify.Watcher
+       client              protocol.UIClient
+       clientCtx           context.Context
+       clientCancel        context.CancelFunc
+       streamNotifications protocol.UI_NotificationsClient
+       isConnected         chan bool
+       alertsChan          chan protocol.Alert
+       socketPath          string
+       unixSockPrefix      string
+       //isAsking is set to true if the client is awaiting a decision from the GUI
+       isAsking     bool
+       isUnixSocket bool
+
+       sync.RWMutex
+}
+
+// NewClient creates and configures a new client.
+func NewClient(socketPath, localConfigFile string, stats *statistics.Statistics, rules *rule.Loader, loggers *loggers.LoggerManager) *Client {
+       if localConfigFile != "" {
+               configFile = localConfigFile
+       }
+       c := &Client{
+               stats:        stats,
+               rules:        rules,
+               isUnixSocket: false,
+               isAsking:     false,
+               isConnected:  make(chan bool),
+               alertsChan:   make(chan protocol.Alert, maxQueuedAlerts),
+       }
+       //for i := 0; i < 4; i++ {
+       go c.alertsDispatcher()
+
+       c.clientCtx, c.clientCancel = context.WithCancel(context.Background())
+
+       if watcher, err := fsnotify.NewWatcher(); err == nil {
+               c.configWatcher = watcher
+       }
+       c.loadDiskConfiguration(false)
+       if socketPath != "" {
+               c.setSocketPath(c.getSocketPath(socketPath))
+       }
+       loggers.Load(clientConfig.Server.Loggers, clientConfig.Stats.Workers)
+       stats.SetLimits(clientConfig.Stats)
+       stats.SetLoggers(loggers)
+
+       return c
+}
+
+// Connect starts the connection poller
+func (c *Client) Connect() {
+       go c.poller()
+}
+
+// Close cancels the running tasks: pinging the server and (re)connection poller.
+func (c *Client) Close() {
+       c.clientCancel()
+}
+
+// ProcMonitorMethod returns the monitor method configured.
+// If it's not present in the config file, it'll return an empty string.
+func (c *Client) ProcMonitorMethod() string {
+       clientConfig.RLock()
+       defer clientConfig.RUnlock()
+       return clientConfig.ProcMonitorMethod
+}
+
+// InterceptUnknown returns
+func (c *Client) InterceptUnknown() bool {
+       clientConfig.RLock()
+       defer clientConfig.RUnlock()
+       return clientConfig.InterceptUnknown
+}
+
+// GetFirewallType returns the firewall to use
+func (c *Client) GetFirewallType() string {
+       clientConfig.RLock()
+       defer clientConfig.RUnlock()
+       if clientConfig.Firewall == "" {
+               return iptables.Name
+       }
+       return clientConfig.Firewall
+}
+
+// DefaultAction returns the default configured action for
+func (c *Client) DefaultAction() rule.Action {
+       isConnected := c.Connected()
+
+       c.RLock()
+       defer c.RUnlock()
+
+       if isConnected {
+               return clientConnectedRule.Action
+       }
+
+       return clientDisconnectedRule.Action
+}
+
+// DefaultDuration returns the default duration configured for a rule.
+// For example it can be: once, always, "until restart".
+func (c *Client) DefaultDuration() rule.Duration {
+       c.RLock()
+       defer c.RUnlock()
+       return clientDisconnectedRule.Duration
+}
+
+// Connected checks if the client has established a connection with the server.
+func (c *Client) Connected() bool {
+       c.RLock()
+       defer c.RUnlock()
+       if c.con == nil || c.con.GetState() != connectivity.Ready {
+               return false
+       }
+       return true
+}
+
+// GetIsAsking returns the isAsking flag
+func (c *Client) GetIsAsking() bool {
+       c.RLock()
+       defer c.RUnlock()
+       return c.isAsking
+}
+
+// SetIsAsking sets the isAsking flag
+func (c *Client) SetIsAsking(flag bool) {
+       c.Lock()
+       defer c.Unlock()
+       c.isAsking = flag
+}
+
+func (c *Client) poller() {
+       log.Debug("UI service poller started for socket %s", c.socketPath)
+       wasConnected := false
+       for {
+               select {
+               case <-c.clientCtx.Done():
+                       log.Info("Client.poller() exit, Done()")
+                       goto Exit
+               default:
+                       isConnected := c.Connected()
+                       if wasConnected != isConnected {
+                               c.onStatusChange(isConnected)
+                               wasConnected = isConnected
+                       }
+
+                       if c.Connected() == false {
+                               // connect and create the client if needed
+                               if err := c.connect(); err != nil {
+                                       log.Warning("Error while connecting to UI service: %s", err)
+                               }
+                       }
+                       if c.Connected() == true {
+                               // if the client is connected and ready, send a ping
+                               if err := c.ping(time.Now()); err != nil {
+                                       log.Warning("Error while pinging UI service: %s, state: %v", err, c.con.GetState())
+                               }
+                       }
+
+                       time.Sleep(1 * time.Second)
+               }
+       }
+Exit:
+       log.Info("uiClient exit")
+}
+
+func (c *Client) onStatusChange(connected bool) {
+       if connected {
+               log.Info("Connected to the UI service on %s", c.socketPath)
+               go c.Subscribe()
+
+               select {
+               case c.isConnected <- true:
+               default:
+               }
+       } else {
+               log.Error("Connection to the UI service lost.")
+               c.disconnect()
+       }
+}
+
+func (c *Client) connect() (err error) {
+       if c.Connected() {
+               return
+       }
+
+       if c.con != nil {
+               if c.con.GetState() == connectivity.TransientFailure || c.con.GetState() == connectivity.Shutdown {
+                       c.disconnect()
+               } else {
+                       return
+               }
+       }
+
+       if err := c.openSocket(); err != nil {
+               log.Debug("connect() %s", err)
+               c.disconnect()
+               return err
+       }
+
+       if c.client == nil {
+               c.client = protocol.NewUIClient(c.con)
+       }
+       return nil
+}
+
+func (c *Client) openSocket() (err error) {
+       c.Lock()
+       defer c.Unlock()
+
+       dialOption, err := auth.New(&clientConfig)
+       if err != nil {
+               return fmt.Errorf("Invalid client auth options: %s", err)
+       }
+       if c.isUnixSocket {
+               c.con, err = grpc.Dial(c.socketPath, dialOption,
+                       grpc.WithDialer(func(addr string, timeout time.Duration) (net.Conn, error) {
+                               return net.DialTimeout(c.unixSockPrefix, addr, timeout)
+                       }))
+       } else {
+               // https://pkg.go.dev/google.golang.org/grpc/keepalive#ClientParameters
+               var kacp = keepalive.ClientParameters{
+                       Time: 5 * time.Second,
+                       // if there's no activity after ^, wait 20s and close
+                       // server timeout is 20s by default.
+                       Timeout: 22 * time.Second,
+                       // send pings even without active streams
+                       PermitWithoutStream: true,
+               }
+
+               c.con, err = grpc.Dial(c.socketPath, dialOption, grpc.WithKeepaliveParams(kacp))
+       }
+
+       return err
+}
+
+func (c *Client) disconnect() {
+       c.Lock()
+       defer c.Unlock()
+
+       select {
+       case c.isConnected <- false:
+       default:
+       }
+       if c.con != nil {
+               c.con.Close()
+               c.con = nil
+               log.Debug("client.disconnect()")
+       }
+       c.client = nil
+}
+
+func (c *Client) ping(ts time.Time) (err error) {
+       if c.Connected() == false {
+               return fmt.Errorf("service is not connected")
+       }
+
+       c.Lock()
+       defer c.Unlock()
+
+       ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+       defer cancel()
+       reqID := uint64(ts.UnixNano())
+
+       pReq := &protocol.PingRequest{
+               Id:    reqID,
+               Stats: c.stats.Serialize(),
+       }
+       c.stats.RLock()
+       pong, err := c.client.Ping(ctx, pReq)
+       c.stats.RUnlock()
+       if err != nil {
+               return err
+       }
+
+       if pong.Id != reqID {
+               return fmt.Errorf("Expected pong with id 0x%x, got 0x%x", reqID, pong.Id)
+       }
+
+       return nil
+}
+
+// Ask sends a request to the server, with the values of a connection to be
+// allowed or denied.
+func (c *Client) Ask(con *conman.Connection) *rule.Rule {
+       if c.client == nil {
+               return nil
+       }
+
+       // FIXME: if timeout is fired, the rule is not added to the list in the GUI
+       ctx, cancel := context.WithTimeout(context.Background(), time.Second*120)
+       defer cancel()
+       reply, err := c.client.AskRule(ctx, con.Serialize())
+       if err != nil {
+               log.Warning("Error while asking for rule: %s - %v", err, con)
+               return nil
+       }
+
+       r, err := rule.Deserialize(reply)
+       if err != nil {
+               return nil
+       }
+       return r
+}
+
+// PostAlert queues a new message to be delivered to the server
+func (c *Client) PostAlert(atype protocol.Alert_Type, awhat protocol.Alert_What, action protocol.Alert_Action, prio protocol.Alert_Priority, data interface{}) {
+       if len(c.alertsChan) > maxQueuedAlerts-1 {
+               // pop oldest alert if channel is full
+               log.Debug("PostAlert() queue full, popping alert (%d)", len(c.alertsChan))
+               <-c.alertsChan
+       }
+       if c.Connected() == false {
+               log.Debug("UI not connected, queueing alert: %d", len(c.alertsChan))
+       }
+       c.alertsChan <- *NewAlert(atype, awhat, action, prio, data)
+}
+
+func (c *Client) monitorConfigWorker() {
+       for {
+               select {
+               case event := <-c.configWatcher.Events:
+                       if (event.Op&fsnotify.Write == fsnotify.Write) || (event.Op&fsnotify.Remove == fsnotify.Remove) {
+                               c.loadDiskConfiguration(true)
+                       }
+               }
+       }
+}
diff --git a/daemon/ui/client_test.go b/daemon/ui/client_test.go
new file mode 100644 (file)
index 0000000..c3de0a6
--- /dev/null
@@ -0,0 +1,91 @@
+package ui
+
+import (
+       "encoding/json"
+       "testing"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/firewall/iptables"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/loggers"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/statistics"
+       "github.com/evilsocket/opensnitch/daemon/ui/config"
+)
+
+var (
+       defaultConfig = &config.Config{
+               ProcMonitorMethod: procmon.MethodEbpf,
+               DefaultAction:     "allow",
+               DefaultDuration:   "once",
+               InterceptUnknown:  false,
+               Firewall:          "nftables",
+       }
+       reloadConfig = *defaultConfig
+)
+
+func restoreConfigFile(t *testing.T) {
+       // start from a clean state
+       if _, err := core.Exec("cp", []string{
+               // unmodified default config
+               "./testdata/orig-default-config.json",
+               // config will be modified by some tests
+               "./testdata/default-config.json",
+       }); err != nil {
+               t.Errorf("error copying default config file: %s", err)
+       }
+}
+
+func validateConfig(t *testing.T, uiClient *Client, cfg *config.Config) {
+       if uiClient.ProcMonitorMethod() != cfg.ProcMonitorMethod {
+               t.Errorf("not expected ProcMonitorMethod value: %s, expected: %s", uiClient.ProcMonitorMethod(), cfg.ProcMonitorMethod)
+       }
+       if uiClient.GetFirewallType() != cfg.Firewall {
+               t.Errorf("not expected FirewallType value: %s, expected: %s", uiClient.GetFirewallType(), cfg.Firewall)
+       }
+       if uiClient.InterceptUnknown() != cfg.InterceptUnknown {
+               t.Errorf("not expected InterceptUnknown value: %v, expected: %v", uiClient.InterceptUnknown(), cfg.InterceptUnknown)
+       }
+       if uiClient.DefaultAction() != rule.Action(cfg.DefaultAction) {
+               t.Errorf("not expected DefaultAction value: %s, expected: %s", clientDisconnectedRule.Action, cfg.DefaultAction)
+       }
+}
+
+func TestClientConfig(t *testing.T) {
+       restoreConfigFile(t)
+       cfgFile := "./testdata/default-config.json"
+
+       rules, err := rule.NewLoader(false)
+       if err != nil {
+               log.Fatal("")
+       }
+
+       stats := statistics.New(rules)
+       loggerMgr := loggers.NewLoggerManager()
+       uiClient := NewClient("unix:///tmp/osui.sock", cfgFile, stats, rules, loggerMgr)
+
+       t.Run("validate-load-config", func(t *testing.T) {
+               validateConfig(t, uiClient, defaultConfig)
+       })
+
+       t.Run("validate-reload-config", func(t *testing.T) {
+               reloadConfig.ProcMonitorMethod = procmon.MethodProc
+               reloadConfig.DefaultAction = string(rule.Deny)
+               reloadConfig.InterceptUnknown = true
+               reloadConfig.Firewall = iptables.Name
+               reloadConfig.Server.Address = "unix:///run/user/1000/opensnitch/osui.sock"
+
+               plainJSON, err := json.Marshal(reloadConfig)
+               if err != nil {
+                       t.Errorf("Error marshalling config: %s", err)
+               }
+               if err = config.Save(configFile, string(plainJSON)); err != nil {
+                       t.Errorf("error saving config to disk: %s", err)
+               }
+               time.Sleep(time.Second * 3)
+
+               validateConfig(t, uiClient, &reloadConfig)
+       })
+}
diff --git a/daemon/ui/config/config.go b/daemon/ui/config/config.go
new file mode 100644 (file)
index 0000000..afeef8a
--- /dev/null
@@ -0,0 +1,117 @@
+package config
+
+import (
+       "encoding/json"
+       "fmt"
+       "io/ioutil"
+       "os"
+       "reflect"
+       "sync"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/log/loggers"
+       "github.com/evilsocket/opensnitch/daemon/statistics"
+)
+
+type (
+       serverTLSOptions struct {
+               CACert     string `json:"CACert"`
+               ServerCert string `json:"ServerCert"`
+               ServerKey  string `json:"ServerKey"`
+               ClientCert string `json:"ClientCert"`
+               ClientKey  string `json:"ClientKey"`
+               // https://pkg.go.dev/crypto/tls#ClientAuthType
+               ClientAuthType string `json:"ClientAuthType"`
+               // https://pkg.go.dev/crypto/tls#Config
+               SkipVerify bool `json:"SkipVerify"`
+
+               // https://pkg.go.dev/crypto/tls#Conn.VerifyHostname
+               // VerifyHostname bool
+               // https://pkg.go.dev/crypto/tls#example-Config-VerifyConnection
+               // VerifyConnection bool
+               // VerifyPeerCertificate bool
+       }
+
+       serverAuth struct {
+               // token?, google?, simple-tls, mutual-tls
+               Type       string           `json:"Type"`
+               TLSOptions serverTLSOptions `json:"TLSOptions"`
+       }
+
+       serverConfig struct {
+               Loggers        []loggers.LoggerConfig `json:"Loggers"`
+               Address        string                 `json:"Address"`
+               LogFile        string                 `json:"LogFile"`
+               Authentication serverAuth             `json:"Authentication"`
+       }
+
+       rulesOptions struct {
+               Path string `json:"Path"`
+       }
+
+       ebpfOptions struct {
+               ModulesPath string `json:"ModulesPath"`
+       }
+
+       // InternalOptions struct
+       internalOptions struct {
+               GCPercent         int  `json:"GCPercent"`
+               FlushConnsOnStart bool `json:"FlushConnsOnStart"`
+       }
+)
+
+// Config holds the values loaded from configFile
+type Config struct {
+       LogLevel          *int32                 `json:"LogLevel"`
+       Firewall          string                 `json:"Firewall"`
+       DefaultAction     string                 `json:"DefaultAction"`
+       DefaultDuration   string                 `json:"DefaultDuration"`
+       ProcMonitorMethod string                 `json:"ProcMonitorMethod"`
+       Ebpf              ebpfOptions            `json:"Ebpf"`
+       Rules             rulesOptions           `json:"Rules"`
+       Server            serverConfig           `json:"Server"`
+       Stats             statistics.StatsConfig `json:"Stats"`
+       Internal          internalOptions        `json:"Internal"`
+
+       InterceptUnknown bool `json:"InterceptUnknown"`
+       LogUTC           bool `json:"LogUTC"`
+       LogMicro         bool `json:"LogMicro"`
+
+       sync.RWMutex
+}
+
+// Parse determines if the given configuration is ok.
+func Parse(rawConfig interface{}) (conf Config, err error) {
+       if vt := reflect.ValueOf(rawConfig).Kind(); vt == reflect.String {
+               err = json.Unmarshal([]byte((rawConfig.(string))), &conf)
+       } else {
+               err = json.Unmarshal(rawConfig.([]uint8), &conf)
+       }
+       return conf, err
+}
+
+// Load loads the content of a file from disk.
+func Load(configFile string) ([]byte, error) {
+       raw, err := ioutil.ReadFile(configFile)
+       if err != nil || len(raw) == 0 {
+               return nil, err
+       }
+
+       return raw, nil
+}
+
+// Save writes daemon configuration to disk.
+func Save(configFile, rawConfig string) (err error) {
+       if _, err = Parse(rawConfig); err != nil {
+               return fmt.Errorf("Error parsing configuration %s: %s", rawConfig, err)
+       }
+
+       if err = os.Chmod(configFile, 0600); err != nil {
+               log.Warning("unable to set permissions to default config: %s", err)
+       }
+       if err = ioutil.WriteFile(configFile, []byte(rawConfig), 0644); err != nil {
+               log.Error("writing configuration to disk: %s", err)
+               return err
+       }
+       return nil
+}
diff --git a/daemon/ui/config_utils.go b/daemon/ui/config_utils.go
new file mode 100644 (file)
index 0000000..a6a1a96
--- /dev/null
@@ -0,0 +1,145 @@
+package ui
+
+import (
+       "fmt"
+       "strings"
+
+       "runtime/debug"
+
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/netlink"
+       "github.com/evilsocket/opensnitch/daemon/procmon/monitor"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/ui/config"
+)
+
+func (c *Client) getSocketPath(socketPath string) string {
+       c.Lock()
+       defer c.Unlock()
+
+       if strings.HasPrefix(socketPath, "unix:") == true {
+               c.isUnixSocket = true
+               c.unixSockPrefix = "unix"
+               return socketPath[5:]
+       }
+       if strings.HasPrefix(socketPath, "unix-abstract:") == true {
+               c.isUnixSocket = true
+               c.unixSockPrefix = "unix-abstract"
+               return socketPath[14:]
+       }
+
+       c.isUnixSocket = false
+       return socketPath
+}
+
+func (c *Client) setSocketPath(socketPath string) {
+       c.Lock()
+       defer c.Unlock()
+
+       c.socketPath = socketPath
+}
+
+func (c *Client) isProcMonitorEqual(newMonitorMethod string) bool {
+       clientConfig.RLock()
+       defer clientConfig.RUnlock()
+
+       return newMonitorMethod == clientConfig.ProcMonitorMethod
+}
+
+func (c *Client) loadDiskConfiguration(reload bool) {
+       raw, err := config.Load(configFile)
+       if err != nil || len(raw) == 0 {
+               // Sometimes we may receive 2 Write events on monitorConfigWorker,
+               // Which may lead to read 0 bytes.
+               log.Warning("Error loading configuration from disk %s: %s", configFile, err)
+               return
+       }
+
+       if ok := c.loadConfiguration(raw); ok {
+               if err := c.configWatcher.Add(configFile); err != nil {
+                       log.Error("Could not watch path: %s", err)
+                       return
+               }
+       }
+
+       if reload {
+               return
+       }
+
+       go c.monitorConfigWorker()
+}
+
+func (c *Client) loadConfiguration(rawConfig []byte) bool {
+       var err error
+       clientConfig, err = config.Parse(rawConfig)
+       if err != nil {
+               msg := fmt.Sprintf("Error parsing configuration %s: %s", configFile, err)
+               log.Error(msg)
+               c.SendWarningAlert(msg)
+               return false
+       }
+
+       clientConfig.Lock()
+       defer clientConfig.Unlock()
+
+       // firstly load config level, to detect further errors if any
+       if clientConfig.LogLevel != nil {
+               log.SetLogLevel(int(*clientConfig.LogLevel))
+       }
+       log.SetLogUTC(clientConfig.LogUTC)
+       log.SetLogMicro(clientConfig.LogMicro)
+       if clientConfig.Server.LogFile != "" {
+               log.Close()
+               log.OpenFile(clientConfig.Server.LogFile)
+       }
+
+       if clientConfig.Server.Address != "" {
+               tempSocketPath := c.getSocketPath(clientConfig.Server.Address)
+               if tempSocketPath != c.socketPath {
+                       // disconnect, and let the connection poller reconnect to the new address
+                       c.disconnect()
+               }
+               c.setSocketPath(tempSocketPath)
+       }
+       if clientConfig.DefaultAction != "" {
+               clientDisconnectedRule.Action = rule.Action(clientConfig.DefaultAction)
+               clientErrorRule.Action = rule.Action(clientConfig.DefaultAction)
+               // TODO: reconfigure connected rule if changed, but not save it to disk.
+               //clientConnectedRule.Action = rule.Action(clientConfig.DefaultAction)
+       }
+       if clientConfig.DefaultDuration != "" {
+               clientDisconnectedRule.Duration = rule.Duration(clientConfig.DefaultDuration)
+               clientErrorRule.Duration = rule.Duration(clientConfig.DefaultDuration)
+       }
+
+       reloaded := false
+       if clientConfig.ProcMonitorMethod != "" {
+               err := monitor.ReconfigureMonitorMethod(clientConfig.ProcMonitorMethod, clientConfig.Ebpf.ModulesPath)
+               if err != nil {
+                       msg := fmt.Sprintf("Unable to set new process monitor (%s) method from disk: %v", clientConfig.ProcMonitorMethod, err.Msg)
+                       log.Warning(msg)
+                       c.SendWarningAlert(msg)
+               } else {
+                       reloaded = true
+               }
+       }
+
+       if reloaded && clientConfig.Internal.FlushConnsOnStart {
+               log.Debug("[config] flushing established connections")
+               netlink.FlushConnections()
+       } else {
+               log.Debug("[config] not flushing established connections")
+       }
+
+       if clientConfig.Internal.GCPercent > 0 {
+               oldgcpercent := debug.SetGCPercent(clientConfig.Internal.GCPercent)
+               log.Info("GC percent set to %d, previously was %d", clientConfig.Internal.GCPercent, oldgcpercent)
+       }
+
+       // TODO:
+       //c.stats.SetLimits(clientConfig.Stats)
+       //loggers.Load(clientConfig.Server.Loggers, clientConfig.Stats.Workers)
+       //stats.SetLoggers(loggers)
+
+       return true
+}
diff --git a/daemon/ui/notifications.go b/daemon/ui/notifications.go
new file mode 100644 (file)
index 0000000..9acb665
--- /dev/null
@@ -0,0 +1,370 @@
+package ui
+
+import (
+       "encoding/json"
+       "fmt"
+       "io"
+       "io/ioutil"
+       "strconv"
+       "strings"
+       "time"
+
+       "github.com/evilsocket/opensnitch/daemon/core"
+       "github.com/evilsocket/opensnitch/daemon/firewall"
+       "github.com/evilsocket/opensnitch/daemon/log"
+       "github.com/evilsocket/opensnitch/daemon/procmon"
+       "github.com/evilsocket/opensnitch/daemon/procmon/monitor"
+       "github.com/evilsocket/opensnitch/daemon/rule"
+       "github.com/evilsocket/opensnitch/daemon/ui/config"
+       "github.com/evilsocket/opensnitch/daemon/ui/protocol"
+       "golang.org/x/net/context"
+)
+
+var stopMonitoringProcess = make(chan int)
+
+// NewReply constructs a new protocol notification reply
+func NewReply(rID uint64, replyCode protocol.NotificationReplyCode, data string) *protocol.NotificationReply {
+       return &protocol.NotificationReply{
+               Id:   rID,
+               Code: replyCode,
+               Data: data,
+       }
+}
+
+func (c *Client) getClientConfig() *protocol.ClientConfig {
+       raw, _ := ioutil.ReadFile(configFile)
+       nodeName := core.GetHostname()
+       nodeVersion := core.GetKernelVersion()
+       var ts time.Time
+       rulesTotal := len(c.rules.GetAll())
+       ruleList := make([]*protocol.Rule, rulesTotal)
+       idx := 0
+       for _, r := range c.rules.GetAll() {
+               ruleList[idx] = r.Serialize()
+               idx++
+       }
+       sysfw, err := firewall.Serialize()
+       if err != nil {
+               log.Warning("firewall.Serialize() error: %s", err)
+       }
+       return &protocol.ClientConfig{
+               Id:                uint64(ts.UnixNano()),
+               Name:              nodeName,
+               Version:           nodeVersion,
+               IsFirewallRunning: firewall.IsRunning(),
+               Config:            strings.Replace(string(raw), "\n", "", -1),
+               LogLevel:          uint32(log.MinLevel),
+               Rules:             ruleList,
+               SystemFirewall:    sysfw,
+       }
+}
+
+func (c *Client) monitorProcessDetails(pid int, stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       p := procmon.NewProcess(pid, "")
+       p.GetInfo()
+       ticker := time.NewTicker(2 * time.Second)
+
+       for {
+               select {
+               case _pid := <-stopMonitoringProcess:
+                       if _pid != pid {
+                               continue
+                       }
+                       goto Exit
+               case <-ticker.C:
+                       if err := p.GetExtraInfo(); err != nil {
+                               c.sendNotificationReply(stream, notification.Id, notification.Data, err)
+                               goto Exit
+                       }
+
+                       pJSON, err := json.Marshal(p)
+                       notification.Data = string(pJSON)
+                       if errs := c.sendNotificationReply(stream, notification.Id, notification.Data, err); errs != nil {
+                               goto Exit
+                       }
+               }
+       }
+
+Exit:
+       ticker.Stop()
+}
+
+func (c *Client) handleActionChangeConfig(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       log.Info("[notification] Reloading configuration")
+       // Parse received configuration first, to get the new proc monitor method.
+       newConf, err := config.Parse(notification.Data)
+       if err != nil {
+               log.Warning("[notification] error parsing received config: %v", notification.Data)
+               c.sendNotificationReply(stream, notification.Id, "", err)
+               return
+       }
+
+       if c.GetFirewallType() != newConf.Firewall {
+               firewall.ChangeFw(newConf.Firewall)
+       }
+
+       if err := monitor.ReconfigureMonitorMethod(
+               newConf.ProcMonitorMethod,
+               clientConfig.Ebpf.ModulesPath,
+       ); err != nil {
+               c.sendNotificationReply(stream, notification.Id, "", err.Msg)
+               return
+       }
+
+       // this save operation triggers a re-loadConfiguration()
+       err = config.Save(configFile, notification.Data)
+       if err != nil {
+               log.Warning("[notification] CHANGE_CONFIG not applied %s", err)
+       }
+
+       c.sendNotificationReply(stream, notification.Id, "", err)
+}
+
+func (c *Client) handleActionEnableRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       var err error
+       for _, rul := range notification.Rules {
+               log.Info("[notification] enable rule: %s", rul.Name)
+               // protocol.Rule(protobuf) != rule.Rule(json)
+               r, _ := rule.Deserialize(rul)
+               r.Enabled = true
+               // save to disk only if the duration is rule.Always
+               err = c.rules.Replace(r, r.Duration == rule.Always)
+       }
+       c.sendNotificationReply(stream, notification.Id, "", err)
+}
+
+func (c *Client) handleActionDisableRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       var err error
+       for _, rul := range notification.Rules {
+               log.Info("[notification] disable rule: %s", rul)
+               r, _ := rule.Deserialize(rul)
+               r.Enabled = false
+               err = c.rules.Replace(r, r.Duration == rule.Always)
+       }
+       c.sendNotificationReply(stream, notification.Id, "", err)
+}
+
+func (c *Client) handleActionChangeRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       var rErr error
+       for _, rul := range notification.Rules {
+               r, err := rule.Deserialize(rul)
+               if r == nil {
+                       rErr = fmt.Errorf("Invalid rule, %s", err)
+                       continue
+               }
+               log.Info("[notification] change rule: %s %d", r, notification.Id)
+               if err := c.rules.Replace(r, r.Duration == rule.Always); err != nil {
+                       log.Warning("[notification] Error changing rule: %s %s", err, r)
+                       rErr = err
+               }
+       }
+       c.sendNotificationReply(stream, notification.Id, "", rErr)
+}
+
+func (c *Client) handleActionDeleteRule(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       var err error
+       for _, rul := range notification.Rules {
+               log.Info("[notification] delete rule: %s %d", rul.Name, notification.Id)
+               err = c.rules.Delete(rul.Name)
+               if err != nil {
+                       log.Error("[notification] Error deleting rule: %s %s", err, rul)
+               }
+       }
+       c.sendNotificationReply(stream, notification.Id, "", err)
+}
+
+func (c *Client) handleActionMonitorProcess(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       pid, err := strconv.Atoi(notification.Data)
+       if err != nil {
+               log.Error("parsing PID to monitor: %d, err: %s", pid, err)
+               return
+       }
+       if !core.Exists(fmt.Sprint("/proc/", pid)) {
+               c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("The process is no longer running"))
+               return
+       }
+       go c.monitorProcessDetails(pid, stream, notification)
+}
+
+func (c *Client) handleActionStopMonitorProcess(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       pid, err := strconv.Atoi(notification.Data)
+       if err != nil {
+               log.Error("parsing PID to stop monitor: %d, err: %s", pid, err)
+               c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error stopping monitor: %s", notification.Data))
+               return
+       }
+       stopMonitoringProcess <- pid
+       c.sendNotificationReply(stream, notification.Id, "", nil)
+}
+
+func (c *Client) handleActionReloadFw(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       log.Info("[notification] reloading firewall")
+
+       sysfw, err := firewall.Deserialize(notification.SysFirewall)
+       if err != nil {
+               log.Warning("firewall.Deserialize() error: %s", err)
+               c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error reloading firewall, invalid rules"))
+               return
+       }
+       if err := firewall.SaveConfiguration(sysfw); err != nil {
+               c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("Error saving system firewall rules: %s", err))
+               return
+       }
+       // TODO:
+       // - add new API endpoints to delete, add or change rules atomically.
+       // - a global goroutine where errors can be sent to the server (GUI).
+       go func(c *Client) {
+               var errors string
+               for {
+                       select {
+                       case fwerr := <-firewall.ErrorsChan():
+                               errors = fmt.Sprint(errors, fwerr, ",")
+                               if firewall.ErrChanEmpty() {
+                                       goto ExitWithError
+                               }
+
+                       // FIXME: can this operation last longer than 2s? if there're more than.. 100...10000 rules?
+                       case <-time.After(2 * time.Second):
+                               log.Debug("[notification] reload firewall. timeout fired, no errors?")
+                               c.sendNotificationReply(stream, notification.Id, "", nil)
+                               goto Exit
+
+                       }
+               }
+       ExitWithError:
+               c.sendNotificationReply(stream, notification.Id, "", fmt.Errorf("%s", errors))
+       Exit:
+       }(c)
+
+}
+
+func (c *Client) handleNotification(stream protocol.UI_NotificationsClient, notification *protocol.Notification) {
+       switch {
+       case notification.Type == protocol.Action_MONITOR_PROCESS:
+               c.handleActionMonitorProcess(stream, notification)
+
+       case notification.Type == protocol.Action_STOP_MONITOR_PROCESS:
+               c.handleActionStopMonitorProcess(stream, notification)
+
+       case notification.Type == protocol.Action_CHANGE_CONFIG:
+               c.handleActionChangeConfig(stream, notification)
+
+       case notification.Type == protocol.Action_ENABLE_INTERCEPTION:
+               log.Info("[notification] starting interception")
+               if err := firewall.EnableInterception(); err != nil {
+                       log.Warning("firewall.EnableInterception() error: %s", err)
+                       c.sendNotificationReply(stream, notification.Id, "", err)
+                       return
+               }
+               c.sendNotificationReply(stream, notification.Id, "", nil)
+
+       case notification.Type == protocol.Action_DISABLE_INTERCEPTION:
+               log.Info("[notification] stopping interception")
+               if err := firewall.DisableInterception(); err != nil {
+                       log.Warning("firewall.DisableInterception() error: %s", err)
+                       c.sendNotificationReply(stream, notification.Id, "", err)
+                       return
+               }
+               c.sendNotificationReply(stream, notification.Id, "", nil)
+
+       case notification.Type == protocol.Action_RELOAD_FW_RULES:
+               c.handleActionReloadFw(stream, notification)
+
+       // ENABLE_RULE just replaces the rule on disk
+       case notification.Type == protocol.Action_ENABLE_RULE:
+               c.handleActionEnableRule(stream, notification)
+
+       case notification.Type == protocol.Action_DISABLE_RULE:
+               c.handleActionDisableRule(stream, notification)
+
+       case notification.Type == protocol.Action_DELETE_RULE:
+               c.handleActionDeleteRule(stream, notification)
+
+       // CHANGE_RULE can add() or replace) an existing rule.
+       case notification.Type == protocol.Action_CHANGE_RULE:
+               c.handleActionChangeRule(stream, notification)
+       }
+}
+
+func (c *Client) sendNotificationReply(stream protocol.UI_NotificationsClient, nID uint64, data string, err error) error {
+       reply := NewReply(nID, protocol.NotificationReplyCode_OK, data)
+       if err != nil {
+               reply.Code = protocol.NotificationReplyCode_ERROR
+               reply.Data = fmt.Sprint(err)
+       }
+       if err := stream.Send(reply); err != nil {
+               log.Error("Error replying to notification: %s %d", err, reply.Id)
+               return err
+       }
+
+       return nil
+}
+
+// Subscribe opens a connection with the server (UI), to start
+// receiving notifications.
+// It firstly sends the daemon status and configuration.
+func (c *Client) Subscribe() {
+       ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
+       defer cancel()
+
+       clientCfg, err := c.client.Subscribe(ctx, c.getClientConfig())
+       if err != nil {
+               log.Error("Subscribing to GUI %s", err)
+               // When connecting to the GUI via TCP, sometimes the notifications channel is
+               // not established, and the main channel is never closed.
+               // We need to disconnect everything after a timeout and try it again.
+               c.disconnect()
+               return
+       }
+
+       if tempConf, err := config.Parse(clientCfg.Config); err == nil {
+               c.Lock()
+               clientConnectedRule.Action = rule.Action(tempConf.DefaultAction)
+               c.Unlock()
+       }
+       c.listenForNotifications()
+}
+
+// Notifications is the channel where the daemon receives messages from the server.
+// It consists of 2 grpc streams (send/receive) that are never closed,
+// this way we can share messages in realtime.
+// If the GUI is closed, we'll receive an error reading from the channel.
+func (c *Client) listenForNotifications() {
+       ctx, cancel := context.WithCancel(context.Background())
+       defer cancel()
+
+       // open the stream channel
+       streamReply := &protocol.NotificationReply{Id: 0, Code: protocol.NotificationReplyCode_OK}
+       notisStream, err := c.client.Notifications(ctx)
+       if err != nil {
+               log.Error("establishing notifications channel %s", err)
+               return
+       }
+       // send the first notification
+       if err := notisStream.Send(streamReply); err != nil {
+               log.Error("sending notification HELLO %s", err)
+               return
+       }
+       log.Info("Start receiving notifications")
+       for {
+               select {
+               case <-c.clientCtx.Done():
+                       goto Exit
+               default:
+                       noti, err := notisStream.Recv()
+                       if err == io.EOF {
+                               log.Warning("notification channel closed by the server")
+                               goto Exit
+                       }
+                       if err != nil {
+                               log.Error("getting notifications: %s %s", err, noti)
+                               goto Exit
+                       }
+                       c.handleNotification(notisStream, noti)
+               }
+       }
+Exit:
+       notisStream.CloseSend()
+       log.Info("Stop receiving notifications")
+       c.disconnect()
+}
diff --git a/daemon/ui/protocol/.gitkeep b/daemon/ui/protocol/.gitkeep
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/daemon/ui/testdata/default-config.json b/daemon/ui/testdata/default-config.json
new file mode 100644 (file)
index 0000000..fca39f7
--- /dev/null
@@ -0,0 +1 @@
+{"Server":{"Address":"unix:///run/user/1000/opensnitch/osui.sock","Authentication":{"Type":"","TLSOptions":{"CACert":"","ServerCert":"","ServerKey":"","ClientCert":"","ClientKey":"","SkipVerify":false,"ClientAuthType":""}},"LogFile":"","Loggers":null},"DefaultAction":"deny","DefaultDuration":"once","InterceptUnknown":true,"ProcMonitorMethod":"proc","LogLevel":null,"LogUTC":false,"LogMicro":false,"Firewall":"iptables","Stats":{"MaxEvents":0,"MaxStats":0,"Workers":0}}
\ No newline at end of file
diff --git a/daemon/ui/testdata/orig-default-config.json b/daemon/ui/testdata/orig-default-config.json
new file mode 100644 (file)
index 0000000..4c461a7
--- /dev/null
@@ -0,0 +1,20 @@
+{
+    "Server":
+    {
+        "Address":"unix:///tmp/osui.sock",
+        "LogFile":"/var/log/opensnitchd.log"
+    },
+    "DefaultAction": "allow",
+    "DefaultDuration": "once",
+    "InterceptUnknown": false,
+    "ProcMonitorMethod": "ebpf",
+    "LogLevel": 2,
+    "LogUTC": true,
+    "LogMicro": false,
+    "Firewall": "nftables",
+    "Stats": {
+        "MaxEvents": 150,
+        "MaxStats": 25,
+        "Workers": 6
+    }
+}
diff --git a/ebpf_prog/Makefile b/ebpf_prog/Makefile
new file mode 100644 (file)
index 0000000..e92aeef
--- /dev/null
@@ -0,0 +1,59 @@
+# OpenSnitch - 2023
+#
+# On Debian based distros we need the following 2 directories.
+# Otherwise, just use the kernel headers from the kernel sources.
+#
+KERNEL_DIR ?= /lib/modules/$(shell uname -r)/source
+KERNEL_HEADERS ?= /usr/src/linux-headers-$(shell uname -r)/
+CLANG ?= clang
+LLC ?= llc
+LLVM_STRIP ?= llvm-strip -g
+ARCH ?= $(shell uname -m)
+
+# as in /usr/src/linux-headers-*/arch/
+# TODO: extract correctly the archs, and add more if needed.
+ifeq ($(ARCH),x86_64)
+       ARCH := x86
+else ifeq ($(ARCH),i686)
+       ARCH := x86
+else ifeq ($(ARCH),armv7l)
+       ARCH := arm
+else ifeq ($(ARCH),aarch64)
+       ARCH := arm64
+endif
+
+ifeq ($(ARCH),arm)
+       # on previous archs, it fails with "SMP not supported on pre-ARMv6"
+       EXTRA_FLAGS = "-D__LINUX_ARM_ARCH__=7"
+endif
+
+BIN := opensnitch.o opensnitch-procs.o opensnitch-dns.o
+CLANG_FLAGS = -I. \
+       -I$(KERNEL_HEADERS)/arch/$(ARCH)/include/generated/ \
+       -I$(KERNEL_HEADERS)/include \
+       -include $(KERNEL_DIR)/include/linux/kconfig.h \
+       -I$(KERNEL_DIR)/include \
+       -I$(KERNEL_DIR)/include/uapi \
+       -I$(KERNEL_DIR)/include/generated/uapi \
+       -I$(KERNEL_DIR)/arch/$(ARCH)/include \
+       -I$(KERNEL_DIR)/arch/$(ARCH)/include/generated \
+       -I$(KERNEL_DIR)/arch/$(ARCH)/include/uapi \
+       -I$(KERNEL_DIR)/arch/$(ARCH)/include/generated/uapi \
+       -I$(KERNEL_DIR)/tools/testing/selftests/bpf/ \
+       -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \
+       -D__TARGET_ARCH_$(ARCH) -Wno-compare-distinct-pointer-types \
+       $(EXTRA_FLAGS) \
+       -Wno-gnu-variable-sized-type-not-at-end \
+       -Wno-address-of-packed-member -Wno-tautological-compare \
+       -Wno-unknown-warning-option  \
+       -g -O2 -emit-llvm
+
+all: $(BIN)
+
+%.o: %.c
+       $(CLANG) $(CLANG_FLAGS) -c $< -o $@.partial
+       $(LLC) -march=bpf -mcpu=generic -filetype=obj -o $@ $@.partial
+       rm -f $@.partial
+
+clean:
+       rm -f *.o *.partial
diff --git a/ebpf_prog/README b/ebpf_prog/README
new file mode 100644 (file)
index 0000000..8cc55b4
--- /dev/null
@@ -0,0 +1,72 @@
+Compilation requires getting kernel sources for now.
+
+There's a helper script to automate this process:
+ https://github.com/evilsocket/opensnitch/blob/master/utils/packaging/build_modules.sh
+
+The basic steps to compile the modules are:
+
+  sudo apt install clang llvm libelf-dev libzip-dev flex bison libssl-dev bc rsync python3
+  cd opensnitch
+  wget https://github.com/torvalds/linux/archive/v5.8.tar.gz
+  tar -xf v5.8.tar.gz
+  cp ebpf_prog/opensnitch*.c ebpf_prog/common* ebpf_prog/Makefile linux-5.8/samples/bpf/
+  cp -r ebpf_prog/bpf_headers/ linux-5.8/samples/bpf/
+  cd linux-5.8 && yes "" | make oldconfig && make prepare && make headers_install # (1 min)
+  cd samples/bpf && make KERNEL_DIR=../../linux-5.8/
+  objdump -h opensnitch.o # you should see many sections, number 1 should be called kprobe/tcp_v4_connect
+  llvm-strip -g opensnitch*.o # remove debug info
+  sudo cp opensnitch*.o /usr/lib/opensnitchd/ebpf/ # or /etc/opensnitchd for < v1.6.x
+  cd ../../../daemon
+
+Since v1.6.0, opensnitchd expects to find the opensnitch*.o modules under:
+ /usr/local/lib/opensnitchd/ebpf/
+ /usr/lib/opensnitchd/ebpf/
+ /etc/opensnitchd/ # deprecated, only on < v1.5.x
+
+start opensnitchd with:
+
+  opensnitchd -rules-path /etc/opensnitchd/rules -process-monitor-method ebpf
+
+---
+
+### Compiling for Fedora (and others rpm based systems)
+
+You need to install the kernel-devel, clang and llvm packages.
+
+Then: `cd ebpf_prog/ ; make KERNEL_DIR=/usr/src/kernels/$(uname -r)/`
+
+(or just pass the kernel version you want)
+
+### Notes
+
+The kernel where you intend to run it must have some options activated:
+
+ $ grep BPF /boot/config-$(uname -r)
+  CONFIG_CGROUP_BPF=y
+  CONFIG_BPF=y
+  CONFIG_BPF_SYSCALL=y
+  CONFIG_BPF_EVENTS=y
+  CONFIG_KPROBES=y
+  CONFIG_KPROBE_EVENTS=y
+
+For the opensnitch-procs.o module to work, this option must be enabled:
+
+ $ grep FTRACE_SYSCALLS /boot/config-$(uname -r)
+  CONFIG_FTRACE_SYSCALLS=y
+
+(https://github.com/iovisor/bcc/blob/master/docs/kernel_config.md)
+
+Also, in some distributions debugfs is not mounted automatically.
+Since v1.6.0 we try to mount it automatically. If you're running
+a lower version so you'll need to mount it manually:
+
+ $ sudo mount -t debugfs none /sys/kernel/debug
+
+In order to make it permanent add it to /etc/fstab:
+
+debugfs    /sys/kernel/debug      debugfs  defaults  0 0
+
+
+opensnitch-procs.o and opensnitch-dns.o are only compatible with kernels >= 5.5,
+bpf_probe_read_user*() were added on that kernel on:
+https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md#helpers
diff --git a/ebpf_prog/arm-clang-asm-fix.patch b/ebpf_prog/arm-clang-asm-fix.patch
new file mode 100644 (file)
index 0000000..d8dd394
--- /dev/null
@@ -0,0 +1,14 @@
+--- ../../arch/arm/include/asm/unified.h       2021-04-20 10:47:54.075834124 +0000
++++ ../../arch/arm/include/asm/unified-clang-fix.h     2021-04-20 10:47:38.943811970 +0000
+@@ -11,7 +11,10 @@
+ #if defined(__ASSEMBLY__)
+       .syntax unified
+ #else
+-__asm__(".syntax unified");
++//__asm__(".syntax unified");
++#ifndef __clang__
++      __asm__(".syntax unified");
++#endif
+ #endif
+ #ifdef CONFIG_CPU_V7M
diff --git a/ebpf_prog/bpf_headers/bpf_core_read.h b/ebpf_prog/bpf_headers/bpf_core_read.h
new file mode 100644 (file)
index 0000000..496e6a8
--- /dev/null
@@ -0,0 +1,484 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+#ifndef __BPF_CORE_READ_H__
+#define __BPF_CORE_READ_H__
+
+/*
+ * enum bpf_field_info_kind is passed as a second argument into
+ * __builtin_preserve_field_info() built-in to get a specific aspect of
+ * a field, captured as a first argument. __builtin_preserve_field_info(field,
+ * info_kind) returns __u32 integer and produces BTF field relocation, which
+ * is understood and processed by libbpf during BPF object loading. See
+ * selftests/bpf for examples.
+ */
+enum bpf_field_info_kind {
+       BPF_FIELD_BYTE_OFFSET = 0,      /* field byte offset */
+       BPF_FIELD_BYTE_SIZE = 1,
+       BPF_FIELD_EXISTS = 2,           /* field existence in target kernel */
+       BPF_FIELD_SIGNED = 3,
+       BPF_FIELD_LSHIFT_U64 = 4,
+       BPF_FIELD_RSHIFT_U64 = 5,
+};
+
+/* second argument to __builtin_btf_type_id() built-in */
+enum bpf_type_id_kind {
+       BPF_TYPE_ID_LOCAL = 0,          /* BTF type ID in local program */
+       BPF_TYPE_ID_TARGET = 1,         /* BTF type ID in target kernel */
+};
+
+/* second argument to __builtin_preserve_type_info() built-in */
+enum bpf_type_info_kind {
+       BPF_TYPE_EXISTS = 0,            /* type existence in target kernel */
+       BPF_TYPE_SIZE = 1,              /* type size in target kernel */
+       BPF_TYPE_MATCHES = 2,           /* type match in target kernel */
+};
+
+/* second argument to __builtin_preserve_enum_value() built-in */
+enum bpf_enum_value_kind {
+       BPF_ENUMVAL_EXISTS = 0,         /* enum value existence in kernel */
+       BPF_ENUMVAL_VALUE = 1,          /* enum value value relocation */
+};
+
+#define __CORE_RELO(src, field, info)                                        \
+       __builtin_preserve_field_info((src)->field, BPF_FIELD_##info)
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define __CORE_BITFIELD_PROBE_READ(dst, src, fld)                            \
+       bpf_probe_read_kernel(                                                \
+                       (void *)dst,                                  \
+                       __CORE_RELO(src, fld, BYTE_SIZE),                     \
+                       (const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
+#else
+/* semantics of LSHIFT_64 assumes loading values into low-ordered bytes, so
+ * for big-endian we need to adjust destination pointer accordingly, based on
+ * field byte size
+ */
+#define __CORE_BITFIELD_PROBE_READ(dst, src, fld)                            \
+       bpf_probe_read_kernel(                                                \
+                       (void *)dst + (8 - __CORE_RELO(src, fld, BYTE_SIZE)), \
+                       __CORE_RELO(src, fld, BYTE_SIZE),                     \
+                       (const void *)src + __CORE_RELO(src, fld, BYTE_OFFSET))
+#endif
+
+/*
+ * Extract bitfield, identified by s->field, and return its value as u64.
+ * All this is done in relocatable manner, so bitfield changes such as
+ * signedness, bit size, offset changes, this will be handled automatically.
+ * This version of macro is using bpf_probe_read_kernel() to read underlying
+ * integer storage. Macro functions as an expression and its return type is
+ * bpf_probe_read_kernel()'s return value: 0, on success, <0 on error.
+ */
+#define BPF_CORE_READ_BITFIELD_PROBED(s, field) ({                           \
+       unsigned long long val = 0;                                           \
+                                                                             \
+       __CORE_BITFIELD_PROBE_READ(&val, s, field);                           \
+       val <<= __CORE_RELO(s, field, LSHIFT_U64);                            \
+       if (__CORE_RELO(s, field, SIGNED))                                    \
+               val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64);  \
+       else                                                                  \
+               val = val >> __CORE_RELO(s, field, RSHIFT_U64);               \
+       val;                                                                  \
+})
+
+/*
+ * Extract bitfield, identified by s->field, and return its value as u64.
+ * This version of macro is using direct memory reads and should be used from
+ * BPF program types that support such functionality (e.g., typed raw
+ * tracepoints).
+ */
+#define BPF_CORE_READ_BITFIELD(s, field) ({                                  \
+       const void *p = (const void *)s + __CORE_RELO(s, field, BYTE_OFFSET); \
+       unsigned long long val;                                               \
+                                                                             \
+       /* This is a so-called barrier_var() operation that makes specified   \
+        * variable "a black box" for optimizing compiler.                    \
+        * It forces compiler to perform BYTE_OFFSET relocation on p and use  \
+        * its calculated value in the switch below, instead of applying      \
+        * the same relocation 4 times for each individual memory load.       \
+        */                                                                   \
+       asm volatile("" : "=r"(p) : "0"(p));                                  \
+                                                                             \
+       switch (__CORE_RELO(s, field, BYTE_SIZE)) {                           \
+       case 1: val = *(const unsigned char *)p; break;                       \
+       case 2: val = *(const unsigned short *)p; break;                      \
+       case 4: val = *(const unsigned int *)p; break;                        \
+       case 8: val = *(const unsigned long long *)p; break;                  \
+       }                                                                     \
+       val <<= __CORE_RELO(s, field, LSHIFT_U64);                            \
+       if (__CORE_RELO(s, field, SIGNED))                                    \
+               val = ((long long)val) >> __CORE_RELO(s, field, RSHIFT_U64);  \
+       else                                                                  \
+               val = val >> __CORE_RELO(s, field, RSHIFT_U64);               \
+       val;                                                                  \
+})
+
+#define ___bpf_field_ref1(field)       (field)
+#define ___bpf_field_ref2(type, field) (((typeof(type) *)0)->field)
+#define ___bpf_field_ref(args...)                                          \
+       ___bpf_apply(___bpf_field_ref, ___bpf_narg(args))(args)
+
+/*
+ * Convenience macro to check that field actually exists in target kernel's.
+ * Returns:
+ *    1, if matching field is present in target kernel;
+ *    0, if no matching field found.
+ *
+ * Supports two forms:
+ *   - field reference through variable access:
+ *     bpf_core_field_exists(p->my_field);
+ *   - field reference through type and field names:
+ *     bpf_core_field_exists(struct my_type, my_field).
+ */
+#define bpf_core_field_exists(field...)                                            \
+       __builtin_preserve_field_info(___bpf_field_ref(field), BPF_FIELD_EXISTS)
+
+/*
+ * Convenience macro to get the byte size of a field. Works for integers,
+ * struct/unions, pointers, arrays, and enums.
+ *
+ * Supports two forms:
+ *   - field reference through variable access:
+ *     bpf_core_field_size(p->my_field);
+ *   - field reference through type and field names:
+ *     bpf_core_field_size(struct my_type, my_field).
+ */
+#define bpf_core_field_size(field...)                                      \
+       __builtin_preserve_field_info(___bpf_field_ref(field), BPF_FIELD_BYTE_SIZE)
+
+/*
+ * Convenience macro to get field's byte offset.
+ *
+ * Supports two forms:
+ *   - field reference through variable access:
+ *     bpf_core_field_offset(p->my_field);
+ *   - field reference through type and field names:
+ *     bpf_core_field_offset(struct my_type, my_field).
+ */
+#define bpf_core_field_offset(field...)                                            \
+       __builtin_preserve_field_info(___bpf_field_ref(field), BPF_FIELD_BYTE_OFFSET)
+
+/*
+ * Convenience macro to get BTF type ID of a specified type, using a local BTF
+ * information. Return 32-bit unsigned integer with type ID from program's own
+ * BTF. Always succeeds.
+ */
+#define bpf_core_type_id_local(type)                                       \
+       __builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_LOCAL)
+
+/*
+ * Convenience macro to get BTF type ID of a target kernel's type that matches
+ * specified local type.
+ * Returns:
+ *    - valid 32-bit unsigned type ID in kernel BTF;
+ *    - 0, if no matching type was found in a target kernel BTF.
+ */
+#define bpf_core_type_id_kernel(type)                                      \
+       __builtin_btf_type_id(*(typeof(type) *)0, BPF_TYPE_ID_TARGET)
+
+/*
+ * Convenience macro to check that provided named type
+ * (struct/union/enum/typedef) exists in a target kernel.
+ * Returns:
+ *    1, if such type is present in target kernel's BTF;
+ *    0, if no matching type is found.
+ */
+#define bpf_core_type_exists(type)                                         \
+       __builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_EXISTS)
+
+/*
+ * Convenience macro to check that provided named type
+ * (struct/union/enum/typedef) "matches" that in a target kernel.
+ * Returns:
+ *    1, if the type matches in the target kernel's BTF;
+ *    0, if the type does not match any in the target kernel
+ */
+#define bpf_core_type_matches(type)                                        \
+       __builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_MATCHES)
+
+/*
+ * Convenience macro to get the byte size of a provided named type
+ * (struct/union/enum/typedef) in a target kernel.
+ * Returns:
+ *    >= 0 size (in bytes), if type is present in target kernel's BTF;
+ *    0, if no matching type is found.
+ */
+#define bpf_core_type_size(type)                                           \
+       __builtin_preserve_type_info(*(typeof(type) *)0, BPF_TYPE_SIZE)
+
+/*
+ * Convenience macro to check that provided enumerator value is defined in
+ * a target kernel.
+ * Returns:
+ *    1, if specified enum type and its enumerator value are present in target
+ *    kernel's BTF;
+ *    0, if no matching enum and/or enum value within that enum is found.
+ */
+#define bpf_core_enum_value_exists(enum_type, enum_value)                  \
+       __builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_EXISTS)
+
+/*
+ * Convenience macro to get the integer value of an enumerator value in
+ * a target kernel.
+ * Returns:
+ *    64-bit value, if specified enum type and its enumerator value are
+ *    present in target kernel's BTF;
+ *    0, if no matching enum and/or enum value within that enum is found.
+ */
+#define bpf_core_enum_value(enum_type, enum_value)                         \
+       __builtin_preserve_enum_value(*(typeof(enum_type) *)enum_value, BPF_ENUMVAL_VALUE)
+
+/*
+ * bpf_core_read() abstracts away bpf_probe_read_kernel() call and captures
+ * offset relocation for source address using __builtin_preserve_access_index()
+ * built-in, provided by Clang.
+ *
+ * __builtin_preserve_access_index() takes as an argument an expression of
+ * taking an address of a field within struct/union. It makes compiler emit
+ * a relocation, which records BTF type ID describing root struct/union and an
+ * accessor string which describes exact embedded field that was used to take
+ * an address. See detailed description of this relocation format and
+ * semantics in comments to struct bpf_field_reloc in libbpf_internal.h.
+ *
+ * This relocation allows libbpf to adjust BPF instruction to use correct
+ * actual field offset, based on target kernel BTF type that matches original
+ * (local) BTF, used to record relocation.
+ */
+#define bpf_core_read(dst, sz, src)                                        \
+       bpf_probe_read_kernel(dst, sz, (const void *)__builtin_preserve_access_index(src))
+
+/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
+#define bpf_core_read_user(dst, sz, src)                                   \
+       bpf_probe_read_user(dst, sz, (const void *)__builtin_preserve_access_index(src))
+/*
+ * bpf_core_read_str() is a thin wrapper around bpf_probe_read_str()
+ * additionally emitting BPF CO-RE field relocation for specified source
+ * argument.
+ */
+#define bpf_core_read_str(dst, sz, src)                                            \
+       bpf_probe_read_kernel_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
+
+/* NOTE: see comments for BPF_CORE_READ_USER() about the proper types use. */
+#define bpf_core_read_user_str(dst, sz, src)                               \
+       bpf_probe_read_user_str(dst, sz, (const void *)__builtin_preserve_access_index(src))
+
+#define ___concat(a, b) a ## b
+#define ___apply(fn, n) ___concat(fn, n)
+#define ___nth(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, __11, N, ...) N
+
+/*
+ * return number of provided arguments; used for switch-based variadic macro
+ * definitions (see ___last, ___arrow, etc below)
+ */
+#define ___narg(...) ___nth(_, ##__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+/*
+ * return 0 if no arguments are passed, N - otherwise; used for
+ * recursively-defined macros to specify termination (0) case, and generic
+ * (N) case (e.g., ___read_ptrs, ___core_read)
+ */
+#define ___empty(...) ___nth(_, ##__VA_ARGS__, N, N, N, N, N, N, N, N, N, N, 0)
+
+#define ___last1(x) x
+#define ___last2(a, x) x
+#define ___last3(a, b, x) x
+#define ___last4(a, b, c, x) x
+#define ___last5(a, b, c, d, x) x
+#define ___last6(a, b, c, d, e, x) x
+#define ___last7(a, b, c, d, e, f, x) x
+#define ___last8(a, b, c, d, e, f, g, x) x
+#define ___last9(a, b, c, d, e, f, g, h, x) x
+#define ___last10(a, b, c, d, e, f, g, h, i, x) x
+#define ___last(...) ___apply(___last, ___narg(__VA_ARGS__))(__VA_ARGS__)
+
+#define ___nolast2(a, _) a
+#define ___nolast3(a, b, _) a, b
+#define ___nolast4(a, b, c, _) a, b, c
+#define ___nolast5(a, b, c, d, _) a, b, c, d
+#define ___nolast6(a, b, c, d, e, _) a, b, c, d, e
+#define ___nolast7(a, b, c, d, e, f, _) a, b, c, d, e, f
+#define ___nolast8(a, b, c, d, e, f, g, _) a, b, c, d, e, f, g
+#define ___nolast9(a, b, c, d, e, f, g, h, _) a, b, c, d, e, f, g, h
+#define ___nolast10(a, b, c, d, e, f, g, h, i, _) a, b, c, d, e, f, g, h, i
+#define ___nolast(...) ___apply(___nolast, ___narg(__VA_ARGS__))(__VA_ARGS__)
+
+#define ___arrow1(a) a
+#define ___arrow2(a, b) a->b
+#define ___arrow3(a, b, c) a->b->c
+#define ___arrow4(a, b, c, d) a->b->c->d
+#define ___arrow5(a, b, c, d, e) a->b->c->d->e
+#define ___arrow6(a, b, c, d, e, f) a->b->c->d->e->f
+#define ___arrow7(a, b, c, d, e, f, g) a->b->c->d->e->f->g
+#define ___arrow8(a, b, c, d, e, f, g, h) a->b->c->d->e->f->g->h
+#define ___arrow9(a, b, c, d, e, f, g, h, i) a->b->c->d->e->f->g->h->i
+#define ___arrow10(a, b, c, d, e, f, g, h, i, j) a->b->c->d->e->f->g->h->i->j
+#define ___arrow(...) ___apply(___arrow, ___narg(__VA_ARGS__))(__VA_ARGS__)
+
+#define ___type(...) typeof(___arrow(__VA_ARGS__))
+
+#define ___read(read_fn, dst, src_type, src, accessor)                     \
+       read_fn((void *)(dst), sizeof(*(dst)), &((src_type)(src))->accessor)
+
+/* "recursively" read a sequence of inner pointers using local __t var */
+#define ___rd_first(fn, src, a) ___read(fn, &__t, ___type(src), src, a);
+#define ___rd_last(fn, ...)                                                \
+       ___read(fn, &__t, ___type(___nolast(__VA_ARGS__)), __t, ___last(__VA_ARGS__));
+#define ___rd_p1(fn, ...) const void *__t; ___rd_first(fn, __VA_ARGS__)
+#define ___rd_p2(fn, ...) ___rd_p1(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p3(fn, ...) ___rd_p2(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p4(fn, ...) ___rd_p3(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p5(fn, ...) ___rd_p4(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p6(fn, ...) ___rd_p5(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p7(fn, ...) ___rd_p6(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p8(fn, ...) ___rd_p7(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___rd_p9(fn, ...) ___rd_p8(fn, ___nolast(__VA_ARGS__)) ___rd_last(fn, __VA_ARGS__)
+#define ___read_ptrs(fn, src, ...)                                         \
+       ___apply(___rd_p, ___narg(__VA_ARGS__))(fn, src, __VA_ARGS__)
+
+#define ___core_read0(fn, fn_ptr, dst, src, a)                             \
+       ___read(fn, dst, ___type(src), src, a);
+#define ___core_readN(fn, fn_ptr, dst, src, ...)                           \
+       ___read_ptrs(fn_ptr, src, ___nolast(__VA_ARGS__))                   \
+       ___read(fn, dst, ___type(src, ___nolast(__VA_ARGS__)), __t,         \
+               ___last(__VA_ARGS__));
+#define ___core_read(fn, fn_ptr, dst, src, a, ...)                         \
+       ___apply(___core_read, ___empty(__VA_ARGS__))(fn, fn_ptr, dst,      \
+                                                     src, a, ##__VA_ARGS__)
+
+/*
+ * BPF_CORE_READ_INTO() is a more performance-conscious variant of
+ * BPF_CORE_READ(), in which final field is read into user-provided storage.
+ * See BPF_CORE_READ() below for more details on general usage.
+ */
+#define BPF_CORE_READ_INTO(dst, src, a, ...) ({                                    \
+       ___core_read(bpf_core_read, bpf_core_read,                          \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/*
+ * Variant of BPF_CORE_READ_INTO() for reading from user-space memory.
+ *
+ * NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
+ */
+#define BPF_CORE_READ_USER_INTO(dst, src, a, ...) ({                       \
+       ___core_read(bpf_core_read_user, bpf_core_read_user,                \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/* Non-CO-RE variant of BPF_CORE_READ_INTO() */
+#define BPF_PROBE_READ_INTO(dst, src, a, ...) ({                           \
+       ___core_read(bpf_probe_read, bpf_probe_read,                        \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/* Non-CO-RE variant of BPF_CORE_READ_USER_INTO().
+ *
+ * As no CO-RE relocations are emitted, source types can be arbitrary and are
+ * not restricted to kernel types only.
+ */
+#define BPF_PROBE_READ_USER_INTO(dst, src, a, ...) ({                      \
+       ___core_read(bpf_probe_read_user, bpf_probe_read_user,              \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/*
+ * BPF_CORE_READ_STR_INTO() does same "pointer chasing" as
+ * BPF_CORE_READ() for intermediate pointers, but then executes (and returns
+ * corresponding error code) bpf_core_read_str() for final string read.
+ */
+#define BPF_CORE_READ_STR_INTO(dst, src, a, ...) ({                        \
+       ___core_read(bpf_core_read_str, bpf_core_read,                      \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/*
+ * Variant of BPF_CORE_READ_STR_INTO() for reading from user-space memory.
+ *
+ * NOTE: see comments for BPF_CORE_READ_USER() about the proper types use.
+ */
+#define BPF_CORE_READ_USER_STR_INTO(dst, src, a, ...) ({                   \
+       ___core_read(bpf_core_read_user_str, bpf_core_read_user,            \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/* Non-CO-RE variant of BPF_CORE_READ_STR_INTO() */
+#define BPF_PROBE_READ_STR_INTO(dst, src, a, ...) ({                       \
+       ___core_read(bpf_probe_read_str, bpf_probe_read,                    \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/*
+ * Non-CO-RE variant of BPF_CORE_READ_USER_STR_INTO().
+ *
+ * As no CO-RE relocations are emitted, source types can be arbitrary and are
+ * not restricted to kernel types only.
+ */
+#define BPF_PROBE_READ_USER_STR_INTO(dst, src, a, ...) ({                  \
+       ___core_read(bpf_probe_read_user_str, bpf_probe_read_user,          \
+                    dst, (src), a, ##__VA_ARGS__)                          \
+})
+
+/*
+ * BPF_CORE_READ() is used to simplify BPF CO-RE relocatable read, especially
+ * when there are few pointer chasing steps.
+ * E.g., what in non-BPF world (or in BPF w/ BCC) would be something like:
+ *     int x = s->a.b.c->d.e->f->g;
+ * can be succinctly achieved using BPF_CORE_READ as:
+ *     int x = BPF_CORE_READ(s, a.b.c, d.e, f, g);
+ *
+ * BPF_CORE_READ will decompose above statement into 4 bpf_core_read (BPF
+ * CO-RE relocatable bpf_probe_read_kernel() wrapper) calls, logically
+ * equivalent to:
+ * 1. const void *__t = s->a.b.c;
+ * 2. __t = __t->d.e;
+ * 3. __t = __t->f;
+ * 4. return __t->g;
+ *
+ * Equivalence is logical, because there is a heavy type casting/preservation
+ * involved, as well as all the reads are happening through
+ * bpf_probe_read_kernel() calls using __builtin_preserve_access_index() to
+ * emit CO-RE relocations.
+ *
+ * N.B. Only up to 9 "field accessors" are supported, which should be more
+ * than enough for any practical purpose.
+ */
+#define BPF_CORE_READ(src, a, ...) ({                                      \
+       ___type((src), a, ##__VA_ARGS__) __r;                               \
+       BPF_CORE_READ_INTO(&__r, (src), a, ##__VA_ARGS__);                  \
+       __r;                                                                \
+})
+
+/*
+ * Variant of BPF_CORE_READ() for reading from user-space memory.
+ *
+ * NOTE: all the source types involved are still *kernel types* and need to
+ * exist in kernel (or kernel module) BTF, otherwise CO-RE relocation will
+ * fail. Custom user types are not relocatable with CO-RE.
+ * The typical situation in which BPF_CORE_READ_USER() might be used is to
+ * read kernel UAPI types from the user-space memory passed in as a syscall
+ * input argument.
+ */
+#define BPF_CORE_READ_USER(src, a, ...) ({                                 \
+       ___type((src), a, ##__VA_ARGS__) __r;                               \
+       BPF_CORE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__);             \
+       __r;                                                                \
+})
+
+/* Non-CO-RE variant of BPF_CORE_READ() */
+#define BPF_PROBE_READ(src, a, ...) ({                                     \
+       ___type((src), a, ##__VA_ARGS__) __r;                               \
+       BPF_PROBE_READ_INTO(&__r, (src), a, ##__VA_ARGS__);                 \
+       __r;                                                                \
+})
+
+/*
+ * Non-CO-RE variant of BPF_CORE_READ_USER().
+ *
+ * As no CO-RE relocations are emitted, source types can be arbitrary and are
+ * not restricted to kernel types only.
+ */
+#define BPF_PROBE_READ_USER(src, a, ...) ({                                \
+       ___type((src), a, ##__VA_ARGS__) __r;                               \
+       BPF_PROBE_READ_USER_INTO(&__r, (src), a, ##__VA_ARGS__);            \
+       __r;                                                                \
+})
+
+#endif
+
diff --git a/ebpf_prog/bpf_headers/bpf_helper_defs.h b/ebpf_prog/bpf_headers/bpf_helper_defs.h
new file mode 100644 (file)
index 0000000..0916f7b
--- /dev/null
@@ -0,0 +1,4582 @@
+/* This is auto-generated file. See bpf_doc.py for details. */
+
+/* Forward declarations of BPF structs */
+struct bpf_fib_lookup;
+struct bpf_sk_lookup;
+struct bpf_perf_event_data;
+struct bpf_perf_event_value;
+struct bpf_pidns_info;
+struct bpf_redir_neigh;
+struct bpf_sock;
+struct bpf_sock_addr;
+struct bpf_sock_ops;
+struct bpf_sock_tuple;
+struct bpf_spin_lock;
+struct bpf_sysctl;
+struct bpf_tcp_sock;
+struct bpf_tunnel_key;
+struct bpf_xfrm_state;
+struct linux_binprm;
+struct pt_regs;
+struct sk_reuseport_md;
+struct sockaddr;
+struct tcphdr;
+struct seq_file;
+struct tcp6_sock;
+struct tcp_sock;
+struct tcp_timewait_sock;
+struct tcp_request_sock;
+struct udp6_sock;
+struct unix_sock;
+struct task_struct;
+struct __sk_buff;
+struct sk_msg_md;
+struct xdp_md;
+struct path;
+struct btf_ptr;
+struct inode;
+struct socket;
+struct file;
+struct bpf_timer;
+struct mptcp_sock;
+struct bpf_dynptr;
+struct iphdr;
+struct ipv6hdr;
+
+/*
+ * bpf_map_lookup_elem
+ *
+ *     Perform a lookup in *map* for an entry associated to *key*.
+ *
+ * Returns
+ *     Map value associated to *key*, or **NULL** if no entry was
+ *     found.
+ */
+static void *(*bpf_map_lookup_elem)(void *map, const void *key) = (void *) 1;
+
+/*
+ * bpf_map_update_elem
+ *
+ *     Add or update the value of the entry associated to *key* in
+ *     *map* with *value*. *flags* is one of:
+ *
+ *     **BPF_NOEXIST**
+ *             The entry for *key* must not exist in the map.
+ *     **BPF_EXIST**
+ *             The entry for *key* must already exist in the map.
+ *     **BPF_ANY**
+ *             No condition on the existence of the entry for *key*.
+ *
+ *     Flag value **BPF_NOEXIST** cannot be used for maps of types
+ *     **BPF_MAP_TYPE_ARRAY** or **BPF_MAP_TYPE_PERCPU_ARRAY**  (all
+ *     elements always exist), the helper would return an error.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_map_update_elem)(void *map, const void *key, const void *value, __u64 flags) = (void *) 2;
+
+/*
+ * bpf_map_delete_elem
+ *
+ *     Delete entry with *key* from *map*.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_map_delete_elem)(void *map, const void *key) = (void *) 3;
+
+/*
+ * bpf_probe_read
+ *
+ *     For tracing programs, safely attempt to read *size* bytes from
+ *     kernel space address *unsafe_ptr* and store the data in *dst*.
+ *
+ *     Generally, use **bpf_probe_read_user**\ () or
+ *     **bpf_probe_read_kernel**\ () instead.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_probe_read)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 4;
+
+/*
+ * bpf_ktime_get_ns
+ *
+ *     Return the time elapsed since system boot, in nanoseconds.
+ *     Does not include time the system was suspended.
+ *     See: **clock_gettime**\ (**CLOCK_MONOTONIC**)
+ *
+ * Returns
+ *     Current *ktime*.
+ */
+static __u64 (*bpf_ktime_get_ns)(void) = (void *) 5;
+
+/*
+ * bpf_trace_printk
+ *
+ *     This helper is a "printk()-like" facility for debugging. It
+ *     prints a message defined by format *fmt* (of size *fmt_size*)
+ *     to file *\/sys/kernel/debug/tracing/trace* from DebugFS, if
+ *     available. It can take up to three additional **u64**
+ *     arguments (as an eBPF helpers, the total number of arguments is
+ *     limited to five).
+ *
+ *     Each time the helper is called, it appends a line to the trace.
+ *     Lines are discarded while *\/sys/kernel/debug/tracing/trace* is
+ *     open, use *\/sys/kernel/debug/tracing/trace_pipe* to avoid this.
+ *     The format of the trace is customizable, and the exact output
+ *     one will get depends on the options set in
+ *     *\/sys/kernel/debug/tracing/trace_options* (see also the
+ *     *README* file under the same directory). However, it usually
+ *     defaults to something like:
+ *
+ *     ::
+ *
+ *             telnet-470   [001] .N.. 419421.045894: 0x00000001: <formatted msg>
+ *
+ *     In the above:
+ *
+ *             * ``telnet`` is the name of the current task.
+ *             * ``470`` is the PID of the current task.
+ *             * ``001`` is the CPU number on which the task is
+ *               running.
+ *             * In ``.N..``, each character refers to a set of
+ *               options (whether irqs are enabled, scheduling
+ *               options, whether hard/softirqs are running, level of
+ *               preempt_disabled respectively). **N** means that
+ *               **TIF_NEED_RESCHED** and **PREEMPT_NEED_RESCHED**
+ *               are set.
+ *             * ``419421.045894`` is a timestamp.
+ *             * ``0x00000001`` is a fake value used by BPF for the
+ *               instruction pointer register.
+ *             * ``<formatted msg>`` is the message formatted with
+ *               *fmt*.
+ *
+ *     The conversion specifiers supported by *fmt* are similar, but
+ *     more limited than for printk(). They are **%d**, **%i**,
+ *     **%u**, **%x**, **%ld**, **%li**, **%lu**, **%lx**, **%lld**,
+ *     **%lli**, **%llu**, **%llx**, **%p**, **%s**. No modifier (size
+ *     of field, padding with zeroes, etc.) is available, and the
+ *     helper will return **-EINVAL** (but print nothing) if it
+ *     encounters an unknown specifier.
+ *
+ *     Also, note that **bpf_trace_printk**\ () is slow, and should
+ *     only be used for debugging purposes. For this reason, a notice
+ *     block (spanning several lines) is printed to kernel logs and
+ *     states that the helper should not be used "for production use"
+ *     the first time this helper is used (or more precisely, when
+ *     **trace_printk**\ () buffers are allocated). For passing values
+ *     to user space, perf events should be preferred.
+ *
+ * Returns
+ *     The number of bytes written to the buffer, or a negative error
+ *     in case of failure.
+ */
+static long (*bpf_trace_printk)(const char *fmt, __u32 fmt_size, ...) = (void *) 6;
+
+/*
+ * bpf_get_prandom_u32
+ *
+ *     Get a pseudo-random number.
+ *
+ *     From a security point of view, this helper uses its own
+ *     pseudo-random internal state, and cannot be used to infer the
+ *     seed of other random functions in the kernel. However, it is
+ *     essential to note that the generator used by the helper is not
+ *     cryptographically secure.
+ *
+ * Returns
+ *     A random 32-bit unsigned value.
+ */
+static __u32 (*bpf_get_prandom_u32)(void) = (void *) 7;
+
+/*
+ * bpf_get_smp_processor_id
+ *
+ *     Get the SMP (symmetric multiprocessing) processor id. Note that
+ *     all programs run with migration disabled, which means that the
+ *     SMP processor id is stable during all the execution of the
+ *     program.
+ *
+ * Returns
+ *     The SMP id of the processor running the program.
+ */
+static __u32 (*bpf_get_smp_processor_id)(void) = (void *) 8;
+
+/*
+ * bpf_skb_store_bytes
+ *
+ *     Store *len* bytes from address *from* into the packet
+ *     associated to *skb*, at *offset*. *flags* are a combination of
+ *     **BPF_F_RECOMPUTE_CSUM** (automatically recompute the
+ *     checksum for the packet after storing the bytes) and
+ *     **BPF_F_INVALIDATE_HASH** (set *skb*\ **->hash**, *skb*\
+ *     **->swhash** and *skb*\ **->l4hash** to 0).
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_store_bytes)(struct __sk_buff *skb, __u32 offset, const void *from, __u32 len, __u64 flags) = (void *) 9;
+
+/*
+ * bpf_l3_csum_replace
+ *
+ *     Recompute the layer 3 (e.g. IP) checksum for the packet
+ *     associated to *skb*. Computation is incremental, so the helper
+ *     must know the former value of the header field that was
+ *     modified (*from*), the new value of this field (*to*), and the
+ *     number of bytes (2 or 4) for this field, stored in *size*.
+ *     Alternatively, it is possible to store the difference between
+ *     the previous and the new values of the header field in *to*, by
+ *     setting *from* and *size* to 0. For both methods, *offset*
+ *     indicates the location of the IP checksum within the packet.
+ *
+ *     This helper works in combination with **bpf_csum_diff**\ (),
+ *     which does not update the checksum in-place, but offers more
+ *     flexibility and can handle sizes larger than 2 or 4 for the
+ *     checksum to update.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_l3_csum_replace)(struct __sk_buff *skb, __u32 offset, __u64 from, __u64 to, __u64 size) = (void *) 10;
+
+/*
+ * bpf_l4_csum_replace
+ *
+ *     Recompute the layer 4 (e.g. TCP, UDP or ICMP) checksum for the
+ *     packet associated to *skb*. Computation is incremental, so the
+ *     helper must know the former value of the header field that was
+ *     modified (*from*), the new value of this field (*to*), and the
+ *     number of bytes (2 or 4) for this field, stored on the lowest
+ *     four bits of *flags*. Alternatively, it is possible to store
+ *     the difference between the previous and the new values of the
+ *     header field in *to*, by setting *from* and the four lowest
+ *     bits of *flags* to 0. For both methods, *offset* indicates the
+ *     location of the IP checksum within the packet. In addition to
+ *     the size of the field, *flags* can be added (bitwise OR) actual
+ *     flags. With **BPF_F_MARK_MANGLED_0**, a null checksum is left
+ *     untouched (unless **BPF_F_MARK_ENFORCE** is added as well), and
+ *     for updates resulting in a null checksum the value is set to
+ *     **CSUM_MANGLED_0** instead. Flag **BPF_F_PSEUDO_HDR** indicates
+ *     the checksum is to be computed against a pseudo-header.
+ *
+ *     This helper works in combination with **bpf_csum_diff**\ (),
+ *     which does not update the checksum in-place, but offers more
+ *     flexibility and can handle sizes larger than 2 or 4 for the
+ *     checksum to update.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_l4_csum_replace)(struct __sk_buff *skb, __u32 offset, __u64 from, __u64 to, __u64 flags) = (void *) 11;
+
+/*
+ * bpf_tail_call
+ *
+ *     This special helper is used to trigger a "tail call", or in
+ *     other words, to jump into another eBPF program. The same stack
+ *     frame is used (but values on stack and in registers for the
+ *     caller are not accessible to the callee). This mechanism allows
+ *     for program chaining, either for raising the maximum number of
+ *     available eBPF instructions, or to execute given programs in
+ *     conditional blocks. For security reasons, there is an upper
+ *     limit to the number of successive tail calls that can be
+ *     performed.
+ *
+ *     Upon call of this helper, the program attempts to jump into a
+ *     program referenced at index *index* in *prog_array_map*, a
+ *     special map of type **BPF_MAP_TYPE_PROG_ARRAY**, and passes
+ *     *ctx*, a pointer to the context.
+ *
+ *     If the call succeeds, the kernel immediately runs the first
+ *     instruction of the new program. This is not a function call,
+ *     and it never returns to the previous program. If the call
+ *     fails, then the helper has no effect, and the caller continues
+ *     to run its subsequent instructions. A call can fail if the
+ *     destination program for the jump does not exist (i.e. *index*
+ *     is superior to the number of entries in *prog_array_map*), or
+ *     if the maximum number of tail calls has been reached for this
+ *     chain of programs. This limit is defined in the kernel by the
+ *     macro **MAX_TAIL_CALL_CNT** (not accessible to user space),
+ *     which is currently set to 33.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_tail_call)(void *ctx, void *prog_array_map, __u32 index) = (void *) 12;
+
+/*
+ * bpf_clone_redirect
+ *
+ *     Clone and redirect the packet associated to *skb* to another
+ *     net device of index *ifindex*. Both ingress and egress
+ *     interfaces can be used for redirection. The **BPF_F_INGRESS**
+ *     value in *flags* is used to make the distinction (ingress path
+ *     is selected if the flag is present, egress path otherwise).
+ *     This is the only flag supported for now.
+ *
+ *     In comparison with **bpf_redirect**\ () helper,
+ *     **bpf_clone_redirect**\ () has the associated cost of
+ *     duplicating the packet buffer, but this can be executed out of
+ *     the eBPF program. Conversely, **bpf_redirect**\ () is more
+ *     efficient, but it is handled through an action code where the
+ *     redirection happens only after the eBPF program has returned.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_clone_redirect)(struct __sk_buff *skb, __u32 ifindex, __u64 flags) = (void *) 13;
+
+/*
+ * bpf_get_current_pid_tgid
+ *
+ *     Get the current pid and tgid.
+ *
+ * Returns
+ *     A 64-bit integer containing the current tgid and pid, and
+ *     created as such:
+ *     *current_task*\ **->tgid << 32 \|**
+ *     *current_task*\ **->pid**.
+ */
+static __u64 (*bpf_get_current_pid_tgid)(void) = (void *) 14;
+
+/*
+ * bpf_get_current_uid_gid
+ *
+ *     Get the current uid and gid.
+ *
+ * Returns
+ *     A 64-bit integer containing the current GID and UID, and
+ *     created as such: *current_gid* **<< 32 \|** *current_uid*.
+ */
+static __u64 (*bpf_get_current_uid_gid)(void) = (void *) 15;
+
+/*
+ * bpf_get_current_comm
+ *
+ *     Copy the **comm** attribute of the current task into *buf* of
+ *     *size_of_buf*. The **comm** attribute contains the name of
+ *     the executable (excluding the path) for the current task. The
+ *     *size_of_buf* must be strictly positive. On success, the
+ *     helper makes sure that the *buf* is NUL-terminated. On failure,
+ *     it is filled with zeroes.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_get_current_comm)(void *buf, __u32 size_of_buf) = (void *) 16;
+
+/*
+ * bpf_get_cgroup_classid
+ *
+ *     Retrieve the classid for the current task, i.e. for the net_cls
+ *     cgroup to which *skb* belongs.
+ *
+ *     This helper can be used on TC egress path, but not on ingress.
+ *
+ *     The net_cls cgroup provides an interface to tag network packets
+ *     based on a user-provided identifier for all traffic coming from
+ *     the tasks belonging to the related cgroup. See also the related
+ *     kernel documentation, available from the Linux sources in file
+ *     *Documentation/admin-guide/cgroup-v1/net_cls.rst*.
+ *
+ *     The Linux kernel has two versions for cgroups: there are
+ *     cgroups v1 and cgroups v2. Both are available to users, who can
+ *     use a mixture of them, but note that the net_cls cgroup is for
+ *     cgroup v1 only. This makes it incompatible with BPF programs
+ *     run on cgroups, which is a cgroup-v2-only feature (a socket can
+ *     only hold data for one version of cgroups at a time).
+ *
+ *     This helper is only available is the kernel was compiled with
+ *     the **CONFIG_CGROUP_NET_CLASSID** configuration option set to
+ *     "**y**" or to "**m**".
+ *
+ * Returns
+ *     The classid, or 0 for the default unconfigured classid.
+ */
+static __u32 (*bpf_get_cgroup_classid)(struct __sk_buff *skb) = (void *) 17;
+
+/*
+ * bpf_skb_vlan_push
+ *
+ *     Push a *vlan_tci* (VLAN tag control information) of protocol
+ *     *vlan_proto* to the packet associated to *skb*, then update
+ *     the checksum. Note that if *vlan_proto* is different from
+ *     **ETH_P_8021Q** and **ETH_P_8021AD**, it is considered to
+ *     be **ETH_P_8021Q**.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_vlan_push)(struct __sk_buff *skb, __be16 vlan_proto, __u16 vlan_tci) = (void *) 18;
+
+/*
+ * bpf_skb_vlan_pop
+ *
+ *     Pop a VLAN header from the packet associated to *skb*.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_vlan_pop)(struct __sk_buff *skb) = (void *) 19;
+
+/*
+ * bpf_skb_get_tunnel_key
+ *
+ *     Get tunnel metadata. This helper takes a pointer *key* to an
+ *     empty **struct bpf_tunnel_key** of **size**, that will be
+ *     filled with tunnel metadata for the packet associated to *skb*.
+ *     The *flags* can be set to **BPF_F_TUNINFO_IPV6**, which
+ *     indicates that the tunnel is based on IPv6 protocol instead of
+ *     IPv4.
+ *
+ *     The **struct bpf_tunnel_key** is an object that generalizes the
+ *     principal parameters used by various tunneling protocols into a
+ *     single struct. This way, it can be used to easily make a
+ *     decision based on the contents of the encapsulation header,
+ *     "summarized" in this struct. In particular, it holds the IP
+ *     address of the remote end (IPv4 or IPv6, depending on the case)
+ *     in *key*\ **->remote_ipv4** or *key*\ **->remote_ipv6**. Also,
+ *     this struct exposes the *key*\ **->tunnel_id**, which is
+ *     generally mapped to a VNI (Virtual Network Identifier), making
+ *     it programmable together with the **bpf_skb_set_tunnel_key**\
+ *     () helper.
+ *
+ *     Let's imagine that the following code is part of a program
+ *     attached to the TC ingress interface, on one end of a GRE
+ *     tunnel, and is supposed to filter out all messages coming from
+ *     remote ends with IPv4 address other than 10.0.0.1:
+ *
+ *     ::
+ *
+ *             int ret;
+ *             struct bpf_tunnel_key key = {};
+ *
+ *             ret = bpf_skb_get_tunnel_key(skb, &key, sizeof(key), 0);
+ *             if (ret < 0)
+ *                     return TC_ACT_SHOT;     // drop packet
+ *
+ *             if (key.remote_ipv4 != 0x0a000001)
+ *                     return TC_ACT_SHOT;     // drop packet
+ *
+ *             return TC_ACT_OK;               // accept packet
+ *
+ *     This interface can also be used with all encapsulation devices
+ *     that can operate in "collect metadata" mode: instead of having
+ *     one network device per specific configuration, the "collect
+ *     metadata" mode only requires a single device where the
+ *     configuration can be extracted from this helper.
+ *
+ *     This can be used together with various tunnels such as VXLan,
+ *     Geneve, GRE or IP in IP (IPIP).
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_get_tunnel_key)(struct __sk_buff *skb, struct bpf_tunnel_key *key, __u32 size, __u64 flags) = (void *) 20;
+
+/*
+ * bpf_skb_set_tunnel_key
+ *
+ *     Populate tunnel metadata for packet associated to *skb.* The
+ *     tunnel metadata is set to the contents of *key*, of *size*. The
+ *     *flags* can be set to a combination of the following values:
+ *
+ *     **BPF_F_TUNINFO_IPV6**
+ *             Indicate that the tunnel is based on IPv6 protocol
+ *             instead of IPv4.
+ *     **BPF_F_ZERO_CSUM_TX**
+ *             For IPv4 packets, add a flag to tunnel metadata
+ *             indicating that checksum computation should be skipped
+ *             and checksum set to zeroes.
+ *     **BPF_F_DONT_FRAGMENT**
+ *             Add a flag to tunnel metadata indicating that the
+ *             packet should not be fragmented.
+ *     **BPF_F_SEQ_NUMBER**
+ *             Add a flag to tunnel metadata indicating that a
+ *             sequence number should be added to tunnel header before
+ *             sending the packet. This flag was added for GRE
+ *             encapsulation, but might be used with other protocols
+ *             as well in the future.
+ *
+ *     Here is a typical usage on the transmit path:
+ *
+ *     ::
+ *
+ *             struct bpf_tunnel_key key;
+ *                  populate key ...
+ *             bpf_skb_set_tunnel_key(skb, &key, sizeof(key), 0);
+ *             bpf_clone_redirect(skb, vxlan_dev_ifindex, 0);
+ *
+ *     See also the description of the **bpf_skb_get_tunnel_key**\ ()
+ *     helper for additional information.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_set_tunnel_key)(struct __sk_buff *skb, struct bpf_tunnel_key *key, __u32 size, __u64 flags) = (void *) 21;
+
+/*
+ * bpf_perf_event_read
+ *
+ *     Read the value of a perf event counter. This helper relies on a
+ *     *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of
+ *     the perf event counter is selected when *map* is updated with
+ *     perf event file descriptors. The *map* is an array whose size
+ *     is the number of available CPUs, and each cell contains a value
+ *     relative to one CPU. The value to retrieve is indicated by
+ *     *flags*, that contains the index of the CPU to look up, masked
+ *     with **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to
+ *     **BPF_F_CURRENT_CPU** to indicate that the value for the
+ *     current CPU should be retrieved.
+ *
+ *     Note that before Linux 4.13, only hardware perf event can be
+ *     retrieved.
+ *
+ *     Also, be aware that the newer helper
+ *     **bpf_perf_event_read_value**\ () is recommended over
+ *     **bpf_perf_event_read**\ () in general. The latter has some ABI
+ *     quirks where error and counter value are used as a return code
+ *     (which is wrong to do since ranges may overlap). This issue is
+ *     fixed with **bpf_perf_event_read_value**\ (), which at the same
+ *     time provides more features over the **bpf_perf_event_read**\
+ *     () interface. Please refer to the description of
+ *     **bpf_perf_event_read_value**\ () for details.
+ *
+ * Returns
+ *     The value of the perf event counter read from the map, or a
+ *     negative error code in case of failure.
+ */
+static __u64 (*bpf_perf_event_read)(void *map, __u64 flags) = (void *) 22;
+
+/*
+ * bpf_redirect
+ *
+ *     Redirect the packet to another net device of index *ifindex*.
+ *     This helper is somewhat similar to **bpf_clone_redirect**\
+ *     (), except that the packet is not cloned, which provides
+ *     increased performance.
+ *
+ *     Except for XDP, both ingress and egress interfaces can be used
+ *     for redirection. The **BPF_F_INGRESS** value in *flags* is used
+ *     to make the distinction (ingress path is selected if the flag
+ *     is present, egress path otherwise). Currently, XDP only
+ *     supports redirection to the egress interface, and accepts no
+ *     flag at all.
+ *
+ *     The same effect can also be attained with the more generic
+ *     **bpf_redirect_map**\ (), which uses a BPF map to store the
+ *     redirect target instead of providing it directly to the helper.
+ *
+ * Returns
+ *     For XDP, the helper returns **XDP_REDIRECT** on success or
+ *     **XDP_ABORTED** on error. For other program types, the values
+ *     are **TC_ACT_REDIRECT** on success or **TC_ACT_SHOT** on
+ *     error.
+ */
+static long (*bpf_redirect)(__u32 ifindex, __u64 flags) = (void *) 23;
+
+/*
+ * bpf_get_route_realm
+ *
+ *     Retrieve the realm or the route, that is to say the
+ *     **tclassid** field of the destination for the *skb*. The
+ *     identifier retrieved is a user-provided tag, similar to the
+ *     one used with the net_cls cgroup (see description for
+ *     **bpf_get_cgroup_classid**\ () helper), but here this tag is
+ *     held by a route (a destination entry), not by a task.
+ *
+ *     Retrieving this identifier works with the clsact TC egress hook
+ *     (see also **tc-bpf(8)**), or alternatively on conventional
+ *     classful egress qdiscs, but not on TC ingress path. In case of
+ *     clsact TC egress hook, this has the advantage that, internally,
+ *     the destination entry has not been dropped yet in the transmit
+ *     path. Therefore, the destination entry does not need to be
+ *     artificially held via **netif_keep_dst**\ () for a classful
+ *     qdisc until the *skb* is freed.
+ *
+ *     This helper is available only if the kernel was compiled with
+ *     **CONFIG_IP_ROUTE_CLASSID** configuration option.
+ *
+ * Returns
+ *     The realm of the route for the packet associated to *skb*, or 0
+ *     if none was found.
+ */
+static __u32 (*bpf_get_route_realm)(struct __sk_buff *skb) = (void *) 24;
+
+/*
+ * bpf_perf_event_output
+ *
+ *     Write raw *data* blob into a special BPF perf event held by
+ *     *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf
+ *     event must have the following attributes: **PERF_SAMPLE_RAW**
+ *     as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and
+ *     **PERF_COUNT_SW_BPF_OUTPUT** as **config**.
+ *
+ *     The *flags* are used to indicate the index in *map* for which
+ *     the value must be put, masked with **BPF_F_INDEX_MASK**.
+ *     Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU**
+ *     to indicate that the index of the current CPU core should be
+ *     used.
+ *
+ *     The value to write, of *size*, is passed through eBPF stack and
+ *     pointed by *data*.
+ *
+ *     The context of the program *ctx* needs also be passed to the
+ *     helper.
+ *
+ *     On user space, a program willing to read the values needs to
+ *     call **perf_event_open**\ () on the perf event (either for
+ *     one or for all CPUs) and to store the file descriptor into the
+ *     *map*. This must be done before the eBPF program can send data
+ *     into it. An example is available in file
+ *     *samples/bpf/trace_output_user.c* in the Linux kernel source
+ *     tree (the eBPF program counterpart is in
+ *     *samples/bpf/trace_output_kern.c*).
+ *
+ *     **bpf_perf_event_output**\ () achieves better performance
+ *     than **bpf_trace_printk**\ () for sharing data with user
+ *     space, and is much better suitable for streaming data from eBPF
+ *     programs.
+ *
+ *     Note that this helper is not restricted to tracing use cases
+ *     and can be used with programs attached to TC or XDP as well,
+ *     where it allows for passing data to user space listeners. Data
+ *     can be:
+ *
+ *     * Only custom structs,
+ *     * Only the packet payload, or
+ *     * A combination of both.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_perf_event_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 25;
+
+/*
+ * bpf_skb_load_bytes
+ *
+ *     This helper was provided as an easy way to load data from a
+ *     packet. It can be used to load *len* bytes from *offset* from
+ *     the packet associated to *skb*, into the buffer pointed by
+ *     *to*.
+ *
+ *     Since Linux 4.7, usage of this helper has mostly been replaced
+ *     by "direct packet access", enabling packet data to be
+ *     manipulated with *skb*\ **->data** and *skb*\ **->data_end**
+ *     pointing respectively to the first byte of packet data and to
+ *     the byte after the last byte of packet data. However, it
+ *     remains useful if one wishes to read large quantities of data
+ *     at once from a packet into the eBPF stack.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_load_bytes)(const void *skb, __u32 offset, void *to, __u32 len) = (void *) 26;
+
+/*
+ * bpf_get_stackid
+ *
+ *     Walk a user or a kernel stack and return its id. To achieve
+ *     this, the helper needs *ctx*, which is a pointer to the context
+ *     on which the tracing program is executed, and a pointer to a
+ *     *map* of type **BPF_MAP_TYPE_STACK_TRACE**.
+ *
+ *     The last argument, *flags*, holds the number of stack frames to
+ *     skip (from 0 to 255), masked with
+ *     **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set
+ *     a combination of the following flags:
+ *
+ *     **BPF_F_USER_STACK**
+ *             Collect a user space stack instead of a kernel stack.
+ *     **BPF_F_FAST_STACK_CMP**
+ *             Compare stacks by hash only.
+ *     **BPF_F_REUSE_STACKID**
+ *             If two different stacks hash into the same *stackid*,
+ *             discard the old one.
+ *
+ *     The stack id retrieved is a 32 bit long integer handle which
+ *     can be further combined with other data (including other stack
+ *     ids) and used as a key into maps. This can be useful for
+ *     generating a variety of graphs (such as flame graphs or off-cpu
+ *     graphs).
+ *
+ *     For walking a stack, this helper is an improvement over
+ *     **bpf_probe_read**\ (), which can be used with unrolled loops
+ *     but is not efficient and consumes a lot of eBPF instructions.
+ *     Instead, **bpf_get_stackid**\ () can collect up to
+ *     **PERF_MAX_STACK_DEPTH** both kernel and user frames. Note that
+ *     this limit can be controlled with the **sysctl** program, and
+ *     that it should be manually increased in order to profile long
+ *     user stacks (such as stacks for Java programs). To do so, use:
+ *
+ *     ::
+ *
+ *             # sysctl kernel.perf_event_max_stack=<new value>
+ *
+ * Returns
+ *     The positive or null stack id on success, or a negative error
+ *     in case of failure.
+ */
+static long (*bpf_get_stackid)(void *ctx, void *map, __u64 flags) = (void *) 27;
+
+/*
+ * bpf_csum_diff
+ *
+ *     Compute a checksum difference, from the raw buffer pointed by
+ *     *from*, of length *from_size* (that must be a multiple of 4),
+ *     towards the raw buffer pointed by *to*, of size *to_size*
+ *     (same remark). An optional *seed* can be added to the value
+ *     (this can be cascaded, the seed may come from a previous call
+ *     to the helper).
+ *
+ *     This is flexible enough to be used in several ways:
+ *
+ *     * With *from_size* == 0, *to_size* > 0 and *seed* set to
+ *       checksum, it can be used when pushing new data.
+ *     * With *from_size* > 0, *to_size* == 0 and *seed* set to
+ *       checksum, it can be used when removing data from a packet.
+ *     * With *from_size* > 0, *to_size* > 0 and *seed* set to 0, it
+ *       can be used to compute a diff. Note that *from_size* and
+ *       *to_size* do not need to be equal.
+ *
+ *     This helper can be used in combination with
+ *     **bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\ (), to
+ *     which one can feed in the difference computed with
+ *     **bpf_csum_diff**\ ().
+ *
+ * Returns
+ *     The checksum result, or a negative error code in case of
+ *     failure.
+ */
+static __s64 (*bpf_csum_diff)(__be32 *from, __u32 from_size, __be32 *to, __u32 to_size, __wsum seed) = (void *) 28;
+
+/*
+ * bpf_skb_get_tunnel_opt
+ *
+ *     Retrieve tunnel options metadata for the packet associated to
+ *     *skb*, and store the raw tunnel option data to the buffer *opt*
+ *     of *size*.
+ *
+ *     This helper can be used with encapsulation devices that can
+ *     operate in "collect metadata" mode (please refer to the related
+ *     note in the description of **bpf_skb_get_tunnel_key**\ () for
+ *     more details). A particular example where this can be used is
+ *     in combination with the Geneve encapsulation protocol, where it
+ *     allows for pushing (with **bpf_skb_get_tunnel_opt**\ () helper)
+ *     and retrieving arbitrary TLVs (Type-Length-Value headers) from
+ *     the eBPF program. This allows for full customization of these
+ *     headers.
+ *
+ * Returns
+ *     The size of the option data retrieved.
+ */
+static long (*bpf_skb_get_tunnel_opt)(struct __sk_buff *skb, void *opt, __u32 size) = (void *) 29;
+
+/*
+ * bpf_skb_set_tunnel_opt
+ *
+ *     Set tunnel options metadata for the packet associated to *skb*
+ *     to the option data contained in the raw buffer *opt* of *size*.
+ *
+ *     See also the description of the **bpf_skb_get_tunnel_opt**\ ()
+ *     helper for additional information.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_set_tunnel_opt)(struct __sk_buff *skb, void *opt, __u32 size) = (void *) 30;
+
+/*
+ * bpf_skb_change_proto
+ *
+ *     Change the protocol of the *skb* to *proto*. Currently
+ *     supported are transition from IPv4 to IPv6, and from IPv6 to
+ *     IPv4. The helper takes care of the groundwork for the
+ *     transition, including resizing the socket buffer. The eBPF
+ *     program is expected to fill the new headers, if any, via
+ *     **skb_store_bytes**\ () and to recompute the checksums with
+ *     **bpf_l3_csum_replace**\ () and **bpf_l4_csum_replace**\
+ *     (). The main case for this helper is to perform NAT64
+ *     operations out of an eBPF program.
+ *
+ *     Internally, the GSO type is marked as dodgy so that headers are
+ *     checked and segments are recalculated by the GSO/GRO engine.
+ *     The size for GSO target is adapted as well.
+ *
+ *     All values for *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_change_proto)(struct __sk_buff *skb, __be16 proto, __u64 flags) = (void *) 31;
+
+/*
+ * bpf_skb_change_type
+ *
+ *     Change the packet type for the packet associated to *skb*. This
+ *     comes down to setting *skb*\ **->pkt_type** to *type*, except
+ *     the eBPF program does not have a write access to *skb*\
+ *     **->pkt_type** beside this helper. Using a helper here allows
+ *     for graceful handling of errors.
+ *
+ *     The major use case is to change incoming *skb*s to
+ *     **PACKET_HOST** in a programmatic way instead of having to
+ *     recirculate via **redirect**\ (..., **BPF_F_INGRESS**), for
+ *     example.
+ *
+ *     Note that *type* only allows certain values. At this time, they
+ *     are:
+ *
+ *     **PACKET_HOST**
+ *             Packet is for us.
+ *     **PACKET_BROADCAST**
+ *             Send packet to all.
+ *     **PACKET_MULTICAST**
+ *             Send packet to group.
+ *     **PACKET_OTHERHOST**
+ *             Send packet to someone else.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_change_type)(struct __sk_buff *skb, __u32 type) = (void *) 32;
+
+/*
+ * bpf_skb_under_cgroup
+ *
+ *     Check whether *skb* is a descendant of the cgroup2 held by
+ *     *map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*.
+ *
+ * Returns
+ *     The return value depends on the result of the test, and can be:
+ *
+ *     * 0, if the *skb* failed the cgroup2 descendant test.
+ *     * 1, if the *skb* succeeded the cgroup2 descendant test.
+ *     * A negative error code, if an error occurred.
+ */
+static long (*bpf_skb_under_cgroup)(struct __sk_buff *skb, void *map, __u32 index) = (void *) 33;
+
+/*
+ * bpf_get_hash_recalc
+ *
+ *     Retrieve the hash of the packet, *skb*\ **->hash**. If it is
+ *     not set, in particular if the hash was cleared due to mangling,
+ *     recompute this hash. Later accesses to the hash can be done
+ *     directly with *skb*\ **->hash**.
+ *
+ *     Calling **bpf_set_hash_invalid**\ (), changing a packet
+ *     prototype with **bpf_skb_change_proto**\ (), or calling
+ *     **bpf_skb_store_bytes**\ () with the
+ *     **BPF_F_INVALIDATE_HASH** are actions susceptible to clear
+ *     the hash and to trigger a new computation for the next call to
+ *     **bpf_get_hash_recalc**\ ().
+ *
+ * Returns
+ *     The 32-bit hash.
+ */
+static __u32 (*bpf_get_hash_recalc)(struct __sk_buff *skb) = (void *) 34;
+
+/*
+ * bpf_get_current_task
+ *
+ *     Get the current task.
+ *
+ * Returns
+ *     A pointer to the current task struct.
+ */
+static __u64 (*bpf_get_current_task)(void) = (void *) 35;
+
+/*
+ * bpf_probe_write_user
+ *
+ *     Attempt in a safe way to write *len* bytes from the buffer
+ *     *src* to *dst* in memory. It only works for threads that are in
+ *     user context, and *dst* must be a valid user space address.
+ *
+ *     This helper should not be used to implement any kind of
+ *     security mechanism because of TOC-TOU attacks, but rather to
+ *     debug, divert, and manipulate execution of semi-cooperative
+ *     processes.
+ *
+ *     Keep in mind that this feature is meant for experiments, and it
+ *     has a risk of crashing the system and running programs.
+ *     Therefore, when an eBPF program using this helper is attached,
+ *     a warning including PID and process name is printed to kernel
+ *     logs.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_probe_write_user)(void *dst, const void *src, __u32 len) = (void *) 36;
+
+/*
+ * bpf_current_task_under_cgroup
+ *
+ *     Check whether the probe is being run is the context of a given
+ *     subset of the cgroup2 hierarchy. The cgroup2 to test is held by
+ *     *map* of type **BPF_MAP_TYPE_CGROUP_ARRAY**, at *index*.
+ *
+ * Returns
+ *     The return value depends on the result of the test, and can be:
+ *
+ *     * 1, if current task belongs to the cgroup2.
+ *     * 0, if current task does not belong to the cgroup2.
+ *     * A negative error code, if an error occurred.
+ */
+static long (*bpf_current_task_under_cgroup)(void *map, __u32 index) = (void *) 37;
+
+/*
+ * bpf_skb_change_tail
+ *
+ *     Resize (trim or grow) the packet associated to *skb* to the
+ *     new *len*. The *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ *     The basic idea is that the helper performs the needed work to
+ *     change the size of the packet, then the eBPF program rewrites
+ *     the rest via helpers like **bpf_skb_store_bytes**\ (),
+ *     **bpf_l3_csum_replace**\ (), **bpf_l3_csum_replace**\ ()
+ *     and others. This helper is a slow path utility intended for
+ *     replies with control messages. And because it is targeted for
+ *     slow path, the helper itself can afford to be slow: it
+ *     implicitly linearizes, unclones and drops offloads from the
+ *     *skb*.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_change_tail)(struct __sk_buff *skb, __u32 len, __u64 flags) = (void *) 38;
+
+/*
+ * bpf_skb_pull_data
+ *
+ *     Pull in non-linear data in case the *skb* is non-linear and not
+ *     all of *len* are part of the linear section. Make *len* bytes
+ *     from *skb* readable and writable. If a zero value is passed for
+ *     *len*, then all bytes in the linear part of *skb* will be made
+ *     readable and writable.
+ *
+ *     This helper is only needed for reading and writing with direct
+ *     packet access.
+ *
+ *     For direct packet access, testing that offsets to access
+ *     are within packet boundaries (test on *skb*\ **->data_end**) is
+ *     susceptible to fail if offsets are invalid, or if the requested
+ *     data is in non-linear parts of the *skb*. On failure the
+ *     program can just bail out, or in the case of a non-linear
+ *     buffer, use a helper to make the data available. The
+ *     **bpf_skb_load_bytes**\ () helper is a first solution to access
+ *     the data. Another one consists in using **bpf_skb_pull_data**
+ *     to pull in once the non-linear parts, then retesting and
+ *     eventually access the data.
+ *
+ *     At the same time, this also makes sure the *skb* is uncloned,
+ *     which is a necessary condition for direct write. As this needs
+ *     to be an invariant for the write part only, the verifier
+ *     detects writes and adds a prologue that is calling
+ *     **bpf_skb_pull_data()** to effectively unclone the *skb* from
+ *     the very beginning in case it is indeed cloned.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_pull_data)(struct __sk_buff *skb, __u32 len) = (void *) 39;
+
+/*
+ * bpf_csum_update
+ *
+ *     Add the checksum *csum* into *skb*\ **->csum** in case the
+ *     driver has supplied a checksum for the entire packet into that
+ *     field. Return an error otherwise. This helper is intended to be
+ *     used in combination with **bpf_csum_diff**\ (), in particular
+ *     when the checksum needs to be updated after data has been
+ *     written into the packet through direct packet access.
+ *
+ * Returns
+ *     The checksum on success, or a negative error code in case of
+ *     failure.
+ */
+static __s64 (*bpf_csum_update)(struct __sk_buff *skb, __wsum csum) = (void *) 40;
+
+/*
+ * bpf_set_hash_invalid
+ *
+ *     Invalidate the current *skb*\ **->hash**. It can be used after
+ *     mangling on headers through direct packet access, in order to
+ *     indicate that the hash is outdated and to trigger a
+ *     recalculation the next time the kernel tries to access this
+ *     hash or when the **bpf_get_hash_recalc**\ () helper is called.
+ *
+ * Returns
+ *     void.
+ */
+static void (*bpf_set_hash_invalid)(struct __sk_buff *skb) = (void *) 41;
+
+/*
+ * bpf_get_numa_node_id
+ *
+ *     Return the id of the current NUMA node. The primary use case
+ *     for this helper is the selection of sockets for the local NUMA
+ *     node, when the program is attached to sockets using the
+ *     **SO_ATTACH_REUSEPORT_EBPF** option (see also **socket(7)**),
+ *     but the helper is also available to other eBPF program types,
+ *     similarly to **bpf_get_smp_processor_id**\ ().
+ *
+ * Returns
+ *     The id of current NUMA node.
+ */
+static long (*bpf_get_numa_node_id)(void) = (void *) 42;
+
+/*
+ * bpf_skb_change_head
+ *
+ *     Grows headroom of packet associated to *skb* and adjusts the
+ *     offset of the MAC header accordingly, adding *len* bytes of
+ *     space. It automatically extends and reallocates memory as
+ *     required.
+ *
+ *     This helper can be used on a layer 3 *skb* to push a MAC header
+ *     for redirection into a layer 2 device.
+ *
+ *     All values for *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_change_head)(struct __sk_buff *skb, __u32 len, __u64 flags) = (void *) 43;
+
+/*
+ * bpf_xdp_adjust_head
+ *
+ *     Adjust (move) *xdp_md*\ **->data** by *delta* bytes. Note that
+ *     it is possible to use a negative value for *delta*. This helper
+ *     can be used to prepare the packet for pushing or popping
+ *     headers.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_xdp_adjust_head)(struct xdp_md *xdp_md, int delta) = (void *) 44;
+
+/*
+ * bpf_probe_read_str
+ *
+ *     Copy a NUL terminated string from an unsafe kernel address
+ *     *unsafe_ptr* to *dst*. See **bpf_probe_read_kernel_str**\ () for
+ *     more details.
+ *
+ *     Generally, use **bpf_probe_read_user_str**\ () or
+ *     **bpf_probe_read_kernel_str**\ () instead.
+ *
+ * Returns
+ *     On success, the strictly positive length of the string,
+ *     including the trailing NUL character. On error, a negative
+ *     value.
+ */
+static long (*bpf_probe_read_str)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 45;
+
+/*
+ * bpf_get_socket_cookie
+ *
+ *     If the **struct sk_buff** pointed by *skb* has a known socket,
+ *     retrieve the cookie (generated by the kernel) of this socket.
+ *     If no cookie has been set yet, generate a new cookie. Once
+ *     generated, the socket cookie remains stable for the life of the
+ *     socket. This helper can be useful for monitoring per socket
+ *     networking traffic statistics as it provides a global socket
+ *     identifier that can be assumed unique.
+ *
+ * Returns
+ *     A 8-byte long unique number on success, or 0 if the socket
+ *     field is missing inside *skb*.
+ */
+static __u64 (*bpf_get_socket_cookie)(void *ctx) = (void *) 46;
+
+/*
+ * bpf_get_socket_uid
+ *
+ *     Get the owner UID of the socked associated to *skb*.
+ *
+ * Returns
+ *     The owner UID of the socket associated to *skb*. If the socket
+ *     is **NULL**, or if it is not a full socket (i.e. if it is a
+ *     time-wait or a request socket instead), **overflowuid** value
+ *     is returned (note that **overflowuid** might also be the actual
+ *     UID value for the socket).
+ */
+static __u32 (*bpf_get_socket_uid)(struct __sk_buff *skb) = (void *) 47;
+
+/*
+ * bpf_set_hash
+ *
+ *     Set the full hash for *skb* (set the field *skb*\ **->hash**)
+ *     to value *hash*.
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_set_hash)(struct __sk_buff *skb, __u32 hash) = (void *) 48;
+
+/*
+ * bpf_setsockopt
+ *
+ *     Emulate a call to **setsockopt()** on the socket associated to
+ *     *bpf_socket*, which must be a full socket. The *level* at
+ *     which the option resides and the name *optname* of the option
+ *     must be specified, see **setsockopt(2)** for more information.
+ *     The option value of length *optlen* is pointed by *optval*.
+ *
+ *     *bpf_socket* should be one of the following:
+ *
+ *     * **struct bpf_sock_ops** for **BPF_PROG_TYPE_SOCK_OPS**.
+ *     * **struct bpf_sock_addr** for **BPF_CGROUP_INET4_CONNECT**
+ *       and **BPF_CGROUP_INET6_CONNECT**.
+ *
+ *     This helper actually implements a subset of **setsockopt()**.
+ *     It supports the following *level*\ s:
+ *
+ *     * **SOL_SOCKET**, which supports the following *optname*\ s:
+ *       **SO_RCVBUF**, **SO_SNDBUF**, **SO_MAX_PACING_RATE**,
+ *       **SO_PRIORITY**, **SO_RCVLOWAT**, **SO_MARK**,
+ *       **SO_BINDTODEVICE**, **SO_KEEPALIVE**.
+ *     * **IPPROTO_TCP**, which supports the following *optname*\ s:
+ *       **TCP_CONGESTION**, **TCP_BPF_IW**,
+ *       **TCP_BPF_SNDCWND_CLAMP**, **TCP_SAVE_SYN**,
+ *       **TCP_KEEPIDLE**, **TCP_KEEPINTVL**, **TCP_KEEPCNT**,
+ *       **TCP_SYNCNT**, **TCP_USER_TIMEOUT**, **TCP_NOTSENT_LOWAT**.
+ *     * **IPPROTO_IP**, which supports *optname* **IP_TOS**.
+ *     * **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_setsockopt)(void *bpf_socket, int level, int optname, void *optval, int optlen) = (void *) 49;
+
+/*
+ * bpf_skb_adjust_room
+ *
+ *     Grow or shrink the room for data in the packet associated to
+ *     *skb* by *len_diff*, and according to the selected *mode*.
+ *
+ *     By default, the helper will reset any offloaded checksum
+ *     indicator of the skb to CHECKSUM_NONE. This can be avoided
+ *     by the following flag:
+ *
+ *     * **BPF_F_ADJ_ROOM_NO_CSUM_RESET**: Do not reset offloaded
+ *       checksum data of the skb to CHECKSUM_NONE.
+ *
+ *     There are two supported modes at this time:
+ *
+ *     * **BPF_ADJ_ROOM_MAC**: Adjust room at the mac layer
+ *       (room space is added or removed below the layer 2 header).
+ *
+ *     * **BPF_ADJ_ROOM_NET**: Adjust room at the network layer
+ *       (room space is added or removed below the layer 3 header).
+ *
+ *     The following flags are supported at this time:
+ *
+ *     * **BPF_F_ADJ_ROOM_FIXED_GSO**: Do not adjust gso_size.
+ *       Adjusting mss in this way is not allowed for datagrams.
+ *
+ *     * **BPF_F_ADJ_ROOM_ENCAP_L3_IPV4**,
+ *       **BPF_F_ADJ_ROOM_ENCAP_L3_IPV6**:
+ *       Any new space is reserved to hold a tunnel header.
+ *       Configure skb offsets and other fields accordingly.
+ *
+ *     * **BPF_F_ADJ_ROOM_ENCAP_L4_GRE**,
+ *       **BPF_F_ADJ_ROOM_ENCAP_L4_UDP**:
+ *       Use with ENCAP_L3 flags to further specify the tunnel type.
+ *
+ *     * **BPF_F_ADJ_ROOM_ENCAP_L2**\ (*len*):
+ *       Use with ENCAP_L3/L4 flags to further specify the tunnel
+ *       type; *len* is the length of the inner MAC header.
+ *
+ *     * **BPF_F_ADJ_ROOM_ENCAP_L2_ETH**:
+ *       Use with BPF_F_ADJ_ROOM_ENCAP_L2 flag to further specify the
+ *       L2 type as Ethernet.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_adjust_room)(struct __sk_buff *skb, __s32 len_diff, __u32 mode, __u64 flags) = (void *) 50;
+
+/*
+ * bpf_redirect_map
+ *
+ *     Redirect the packet to the endpoint referenced by *map* at
+ *     index *key*. Depending on its type, this *map* can contain
+ *     references to net devices (for forwarding packets through other
+ *     ports), or to CPUs (for redirecting XDP frames to another CPU;
+ *     but this is only implemented for native XDP (with driver
+ *     support) as of this writing).
+ *
+ *     The lower two bits of *flags* are used as the return code if
+ *     the map lookup fails. This is so that the return value can be
+ *     one of the XDP program return codes up to **XDP_TX**, as chosen
+ *     by the caller. The higher bits of *flags* can be set to
+ *     BPF_F_BROADCAST or BPF_F_EXCLUDE_INGRESS as defined below.
+ *
+ *     With BPF_F_BROADCAST the packet will be broadcasted to all the
+ *     interfaces in the map, with BPF_F_EXCLUDE_INGRESS the ingress
+ *     interface will be excluded when do broadcasting.
+ *
+ *     See also **bpf_redirect**\ (), which only supports redirecting
+ *     to an ifindex, but doesn't require a map to do so.
+ *
+ * Returns
+ *     **XDP_REDIRECT** on success, or the value of the two lower bits
+ *     of the *flags* argument on error.
+ */
+static long (*bpf_redirect_map)(void *map, __u32 key, __u64 flags) = (void *) 51;
+
+/*
+ * bpf_sk_redirect_map
+ *
+ *     Redirect the packet to the socket referenced by *map* (of type
+ *     **BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and
+ *     egress interfaces can be used for redirection. The
+ *     **BPF_F_INGRESS** value in *flags* is used to make the
+ *     distinction (ingress path is selected if the flag is present,
+ *     egress path otherwise). This is the only flag supported for now.
+ *
+ * Returns
+ *     **SK_PASS** on success, or **SK_DROP** on error.
+ */
+static long (*bpf_sk_redirect_map)(struct __sk_buff *skb, void *map, __u32 key, __u64 flags) = (void *) 52;
+
+/*
+ * bpf_sock_map_update
+ *
+ *     Add an entry to, or update a *map* referencing sockets. The
+ *     *skops* is used as a new value for the entry associated to
+ *     *key*. *flags* is one of:
+ *
+ *     **BPF_NOEXIST**
+ *             The entry for *key* must not exist in the map.
+ *     **BPF_EXIST**
+ *             The entry for *key* must already exist in the map.
+ *     **BPF_ANY**
+ *             No condition on the existence of the entry for *key*.
+ *
+ *     If the *map* has eBPF programs (parser and verdict), those will
+ *     be inherited by the socket being added. If the socket is
+ *     already attached to eBPF programs, this results in an error.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_sock_map_update)(struct bpf_sock_ops *skops, void *map, void *key, __u64 flags) = (void *) 53;
+
+/*
+ * bpf_xdp_adjust_meta
+ *
+ *     Adjust the address pointed by *xdp_md*\ **->data_meta** by
+ *     *delta* (which can be positive or negative). Note that this
+ *     operation modifies the address stored in *xdp_md*\ **->data**,
+ *     so the latter must be loaded only after the helper has been
+ *     called.
+ *
+ *     The use of *xdp_md*\ **->data_meta** is optional and programs
+ *     are not required to use it. The rationale is that when the
+ *     packet is processed with XDP (e.g. as DoS filter), it is
+ *     possible to push further meta data along with it before passing
+ *     to the stack, and to give the guarantee that an ingress eBPF
+ *     program attached as a TC classifier on the same device can pick
+ *     this up for further post-processing. Since TC works with socket
+ *     buffers, it remains possible to set from XDP the **mark** or
+ *     **priority** pointers, or other pointers for the socket buffer.
+ *     Having this scratch space generic and programmable allows for
+ *     more flexibility as the user is free to store whatever meta
+ *     data they need.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_xdp_adjust_meta)(struct xdp_md *xdp_md, int delta) = (void *) 54;
+
+/*
+ * bpf_perf_event_read_value
+ *
+ *     Read the value of a perf event counter, and store it into *buf*
+ *     of size *buf_size*. This helper relies on a *map* of type
+ *     **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. The nature of the perf event
+ *     counter is selected when *map* is updated with perf event file
+ *     descriptors. The *map* is an array whose size is the number of
+ *     available CPUs, and each cell contains a value relative to one
+ *     CPU. The value to retrieve is indicated by *flags*, that
+ *     contains the index of the CPU to look up, masked with
+ *     **BPF_F_INDEX_MASK**. Alternatively, *flags* can be set to
+ *     **BPF_F_CURRENT_CPU** to indicate that the value for the
+ *     current CPU should be retrieved.
+ *
+ *     This helper behaves in a way close to
+ *     **bpf_perf_event_read**\ () helper, save that instead of
+ *     just returning the value observed, it fills the *buf*
+ *     structure. This allows for additional data to be retrieved: in
+ *     particular, the enabled and running times (in *buf*\
+ *     **->enabled** and *buf*\ **->running**, respectively) are
+ *     copied. In general, **bpf_perf_event_read_value**\ () is
+ *     recommended over **bpf_perf_event_read**\ (), which has some
+ *     ABI issues and provides fewer functionalities.
+ *
+ *     These values are interesting, because hardware PMU (Performance
+ *     Monitoring Unit) counters are limited resources. When there are
+ *     more PMU based perf events opened than available counters,
+ *     kernel will multiplex these events so each event gets certain
+ *     percentage (but not all) of the PMU time. In case that
+ *     multiplexing happens, the number of samples or counter value
+ *     will not reflect the case compared to when no multiplexing
+ *     occurs. This makes comparison between different runs difficult.
+ *     Typically, the counter value should be normalized before
+ *     comparing to other experiments. The usual normalization is done
+ *     as follows.
+ *
+ *     ::
+ *
+ *             normalized_counter = counter * t_enabled / t_running
+ *
+ *     Where t_enabled is the time enabled for event and t_running is
+ *     the time running for event since last normalization. The
+ *     enabled and running times are accumulated since the perf event
+ *     open. To achieve scaling factor between two invocations of an
+ *     eBPF program, users can use CPU id as the key (which is
+ *     typical for perf array usage model) to remember the previous
+ *     value and do the calculation inside the eBPF program.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_perf_event_read_value)(void *map, __u64 flags, struct bpf_perf_event_value *buf, __u32 buf_size) = (void *) 55;
+
+/*
+ * bpf_perf_prog_read_value
+ *
+ *     For en eBPF program attached to a perf event, retrieve the
+ *     value of the event counter associated to *ctx* and store it in
+ *     the structure pointed by *buf* and of size *buf_size*. Enabled
+ *     and running times are also stored in the structure (see
+ *     description of helper **bpf_perf_event_read_value**\ () for
+ *     more details).
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_perf_prog_read_value)(struct bpf_perf_event_data *ctx, struct bpf_perf_event_value *buf, __u32 buf_size) = (void *) 56;
+
+/*
+ * bpf_getsockopt
+ *
+ *     Emulate a call to **getsockopt()** on the socket associated to
+ *     *bpf_socket*, which must be a full socket. The *level* at
+ *     which the option resides and the name *optname* of the option
+ *     must be specified, see **getsockopt(2)** for more information.
+ *     The retrieved value is stored in the structure pointed by
+ *     *opval* and of length *optlen*.
+ *
+ *     *bpf_socket* should be one of the following:
+ *
+ *     * **struct bpf_sock_ops** for **BPF_PROG_TYPE_SOCK_OPS**.
+ *     * **struct bpf_sock_addr** for **BPF_CGROUP_INET4_CONNECT**
+ *       and **BPF_CGROUP_INET6_CONNECT**.
+ *
+ *     This helper actually implements a subset of **getsockopt()**.
+ *     It supports the following *level*\ s:
+ *
+ *     * **IPPROTO_TCP**, which supports *optname*
+ *       **TCP_CONGESTION**.
+ *     * **IPPROTO_IP**, which supports *optname* **IP_TOS**.
+ *     * **IPPROTO_IPV6**, which supports *optname* **IPV6_TCLASS**.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_getsockopt)(void *bpf_socket, int level, int optname, void *optval, int optlen) = (void *) 57;
+
+/*
+ * bpf_override_return
+ *
+ *     Used for error injection, this helper uses kprobes to override
+ *     the return value of the probed function, and to set it to *rc*.
+ *     The first argument is the context *regs* on which the kprobe
+ *     works.
+ *
+ *     This helper works by setting the PC (program counter)
+ *     to an override function which is run in place of the original
+ *     probed function. This means the probed function is not run at
+ *     all. The replacement function just returns with the required
+ *     value.
+ *
+ *     This helper has security implications, and thus is subject to
+ *     restrictions. It is only available if the kernel was compiled
+ *     with the **CONFIG_BPF_KPROBE_OVERRIDE** configuration
+ *     option, and in this case it only works on functions tagged with
+ *     **ALLOW_ERROR_INJECTION** in the kernel code.
+ *
+ *     Also, the helper is only available for the architectures having
+ *     the CONFIG_FUNCTION_ERROR_INJECTION option. As of this writing,
+ *     x86 architecture is the only one to support this feature.
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_override_return)(struct pt_regs *regs, __u64 rc) = (void *) 58;
+
+/*
+ * bpf_sock_ops_cb_flags_set
+ *
+ *     Attempt to set the value of the **bpf_sock_ops_cb_flags** field
+ *     for the full TCP socket associated to *bpf_sock_ops* to
+ *     *argval*.
+ *
+ *     The primary use of this field is to determine if there should
+ *     be calls to eBPF programs of type
+ *     **BPF_PROG_TYPE_SOCK_OPS** at various points in the TCP
+ *     code. A program of the same type can change its value, per
+ *     connection and as necessary, when the connection is
+ *     established. This field is directly accessible for reading, but
+ *     this helper must be used for updates in order to return an
+ *     error if an eBPF program tries to set a callback that is not
+ *     supported in the current kernel.
+ *
+ *     *argval* is a flag array which can combine these flags:
+ *
+ *     * **BPF_SOCK_OPS_RTO_CB_FLAG** (retransmission time out)
+ *     * **BPF_SOCK_OPS_RETRANS_CB_FLAG** (retransmission)
+ *     * **BPF_SOCK_OPS_STATE_CB_FLAG** (TCP state change)
+ *     * **BPF_SOCK_OPS_RTT_CB_FLAG** (every RTT)
+ *
+ *     Therefore, this function can be used to clear a callback flag by
+ *     setting the appropriate bit to zero. e.g. to disable the RTO
+ *     callback:
+ *
+ *     **bpf_sock_ops_cb_flags_set(bpf_sock,**
+ *             **bpf_sock->bpf_sock_ops_cb_flags & ~BPF_SOCK_OPS_RTO_CB_FLAG)**
+ *
+ *     Here are some examples of where one could call such eBPF
+ *     program:
+ *
+ *     * When RTO fires.
+ *     * When a packet is retransmitted.
+ *     * When the connection terminates.
+ *     * When a packet is sent.
+ *     * When a packet is received.
+ *
+ * Returns
+ *     Code **-EINVAL** if the socket is not a full TCP socket;
+ *     otherwise, a positive number containing the bits that could not
+ *     be set is returned (which comes down to 0 if all bits were set
+ *     as required).
+ */
+static long (*bpf_sock_ops_cb_flags_set)(struct bpf_sock_ops *bpf_sock, int argval) = (void *) 59;
+
+/*
+ * bpf_msg_redirect_map
+ *
+ *     This helper is used in programs implementing policies at the
+ *     socket level. If the message *msg* is allowed to pass (i.e. if
+ *     the verdict eBPF program returns **SK_PASS**), redirect it to
+ *     the socket referenced by *map* (of type
+ *     **BPF_MAP_TYPE_SOCKMAP**) at index *key*. Both ingress and
+ *     egress interfaces can be used for redirection. The
+ *     **BPF_F_INGRESS** value in *flags* is used to make the
+ *     distinction (ingress path is selected if the flag is present,
+ *     egress path otherwise). This is the only flag supported for now.
+ *
+ * Returns
+ *     **SK_PASS** on success, or **SK_DROP** on error.
+ */
+static long (*bpf_msg_redirect_map)(struct sk_msg_md *msg, void *map, __u32 key, __u64 flags) = (void *) 60;
+
+/*
+ * bpf_msg_apply_bytes
+ *
+ *     For socket policies, apply the verdict of the eBPF program to
+ *     the next *bytes* (number of bytes) of message *msg*.
+ *
+ *     For example, this helper can be used in the following cases:
+ *
+ *     * A single **sendmsg**\ () or **sendfile**\ () system call
+ *       contains multiple logical messages that the eBPF program is
+ *       supposed to read and for which it should apply a verdict.
+ *     * An eBPF program only cares to read the first *bytes* of a
+ *       *msg*. If the message has a large payload, then setting up
+ *       and calling the eBPF program repeatedly for all bytes, even
+ *       though the verdict is already known, would create unnecessary
+ *       overhead.
+ *
+ *     When called from within an eBPF program, the helper sets a
+ *     counter internal to the BPF infrastructure, that is used to
+ *     apply the last verdict to the next *bytes*. If *bytes* is
+ *     smaller than the current data being processed from a
+ *     **sendmsg**\ () or **sendfile**\ () system call, the first
+ *     *bytes* will be sent and the eBPF program will be re-run with
+ *     the pointer for start of data pointing to byte number *bytes*
+ *     **+ 1**. If *bytes* is larger than the current data being
+ *     processed, then the eBPF verdict will be applied to multiple
+ *     **sendmsg**\ () or **sendfile**\ () calls until *bytes* are
+ *     consumed.
+ *
+ *     Note that if a socket closes with the internal counter holding
+ *     a non-zero value, this is not a problem because data is not
+ *     being buffered for *bytes* and is sent as it is received.
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_msg_apply_bytes)(struct sk_msg_md *msg, __u32 bytes) = (void *) 61;
+
+/*
+ * bpf_msg_cork_bytes
+ *
+ *     For socket policies, prevent the execution of the verdict eBPF
+ *     program for message *msg* until *bytes* (byte number) have been
+ *     accumulated.
+ *
+ *     This can be used when one needs a specific number of bytes
+ *     before a verdict can be assigned, even if the data spans
+ *     multiple **sendmsg**\ () or **sendfile**\ () calls. The extreme
+ *     case would be a user calling **sendmsg**\ () repeatedly with
+ *     1-byte long message segments. Obviously, this is bad for
+ *     performance, but it is still valid. If the eBPF program needs
+ *     *bytes* bytes to validate a header, this helper can be used to
+ *     prevent the eBPF program to be called again until *bytes* have
+ *     been accumulated.
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_msg_cork_bytes)(struct sk_msg_md *msg, __u32 bytes) = (void *) 62;
+
+/*
+ * bpf_msg_pull_data
+ *
+ *     For socket policies, pull in non-linear data from user space
+ *     for *msg* and set pointers *msg*\ **->data** and *msg*\
+ *     **->data_end** to *start* and *end* bytes offsets into *msg*,
+ *     respectively.
+ *
+ *     If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a
+ *     *msg* it can only parse data that the (**data**, **data_end**)
+ *     pointers have already consumed. For **sendmsg**\ () hooks this
+ *     is likely the first scatterlist element. But for calls relying
+ *     on the **sendpage** handler (e.g. **sendfile**\ ()) this will
+ *     be the range (**0**, **0**) because the data is shared with
+ *     user space and by default the objective is to avoid allowing
+ *     user space to modify data while (or after) eBPF verdict is
+ *     being decided. This helper can be used to pull in data and to
+ *     set the start and end pointer to given values. Data will be
+ *     copied if necessary (i.e. if data was not linear and if start
+ *     and end pointers do not point to the same chunk).
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ *     All values for *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_msg_pull_data)(struct sk_msg_md *msg, __u32 start, __u32 end, __u64 flags) = (void *) 63;
+
+/*
+ * bpf_bind
+ *
+ *     Bind the socket associated to *ctx* to the address pointed by
+ *     *addr*, of length *addr_len*. This allows for making outgoing
+ *     connection from the desired IP address, which can be useful for
+ *     example when all processes inside a cgroup should use one
+ *     single IP address on a host that has multiple IP configured.
+ *
+ *     This helper works for IPv4 and IPv6, TCP and UDP sockets. The
+ *     domain (*addr*\ **->sa_family**) must be **AF_INET** (or
+ *     **AF_INET6**). It's advised to pass zero port (**sin_port**
+ *     or **sin6_port**) which triggers IP_BIND_ADDRESS_NO_PORT-like
+ *     behavior and lets the kernel efficiently pick up an unused
+ *     port as long as 4-tuple is unique. Passing non-zero port might
+ *     lead to degraded performance.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_bind)(struct bpf_sock_addr *ctx, struct sockaddr *addr, int addr_len) = (void *) 64;
+
+/*
+ * bpf_xdp_adjust_tail
+ *
+ *     Adjust (move) *xdp_md*\ **->data_end** by *delta* bytes. It is
+ *     possible to both shrink and grow the packet tail.
+ *     Shrink done via *delta* being a negative integer.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_xdp_adjust_tail)(struct xdp_md *xdp_md, int delta) = (void *) 65;
+
+/*
+ * bpf_skb_get_xfrm_state
+ *
+ *     Retrieve the XFRM state (IP transform framework, see also
+ *     **ip-xfrm(8)**) at *index* in XFRM "security path" for *skb*.
+ *
+ *     The retrieved value is stored in the **struct bpf_xfrm_state**
+ *     pointed by *xfrm_state* and of length *size*.
+ *
+ *     All values for *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ *     This helper is available only if the kernel was compiled with
+ *     **CONFIG_XFRM** configuration option.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_get_xfrm_state)(struct __sk_buff *skb, __u32 index, struct bpf_xfrm_state *xfrm_state, __u32 size, __u64 flags) = (void *) 66;
+
+/*
+ * bpf_get_stack
+ *
+ *     Return a user or a kernel stack in bpf program provided buffer.
+ *     To achieve this, the helper needs *ctx*, which is a pointer
+ *     to the context on which the tracing program is executed.
+ *     To store the stacktrace, the bpf program provides *buf* with
+ *     a nonnegative *size*.
+ *
+ *     The last argument, *flags*, holds the number of stack frames to
+ *     skip (from 0 to 255), masked with
+ *     **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set
+ *     the following flags:
+ *
+ *     **BPF_F_USER_STACK**
+ *             Collect a user space stack instead of a kernel stack.
+ *     **BPF_F_USER_BUILD_ID**
+ *             Collect buildid+offset instead of ips for user stack,
+ *             only valid if **BPF_F_USER_STACK** is also specified.
+ *
+ *     **bpf_get_stack**\ () can collect up to
+ *     **PERF_MAX_STACK_DEPTH** both kernel and user frames, subject
+ *     to sufficient large buffer size. Note that
+ *     this limit can be controlled with the **sysctl** program, and
+ *     that it should be manually increased in order to profile long
+ *     user stacks (such as stacks for Java programs). To do so, use:
+ *
+ *     ::
+ *
+ *             # sysctl kernel.perf_event_max_stack=<new value>
+ *
+ * Returns
+ *     The non-negative copied *buf* length equal to or less than
+ *     *size* on success, or a negative error in case of failure.
+ */
+static long (*bpf_get_stack)(void *ctx, void *buf, __u32 size, __u64 flags) = (void *) 67;
+
+/*
+ * bpf_skb_load_bytes_relative
+ *
+ *     This helper is similar to **bpf_skb_load_bytes**\ () in that
+ *     it provides an easy way to load *len* bytes from *offset*
+ *     from the packet associated to *skb*, into the buffer pointed
+ *     by *to*. The difference to **bpf_skb_load_bytes**\ () is that
+ *     a fifth argument *start_header* exists in order to select a
+ *     base offset to start from. *start_header* can be one of:
+ *
+ *     **BPF_HDR_START_MAC**
+ *             Base offset to load data from is *skb*'s mac header.
+ *     **BPF_HDR_START_NET**
+ *             Base offset to load data from is *skb*'s network header.
+ *
+ *     In general, "direct packet access" is the preferred method to
+ *     access packet data, however, this helper is in particular useful
+ *     in socket filters where *skb*\ **->data** does not always point
+ *     to the start of the mac header and where "direct packet access"
+ *     is not available.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_load_bytes_relative)(const void *skb, __u32 offset, void *to, __u32 len, __u32 start_header) = (void *) 68;
+
+/*
+ * bpf_fib_lookup
+ *
+ *     Do FIB lookup in kernel tables using parameters in *params*.
+ *     If lookup is successful and result shows packet is to be
+ *     forwarded, the neighbor tables are searched for the nexthop.
+ *     If successful (ie., FIB lookup shows forwarding and nexthop
+ *     is resolved), the nexthop address is returned in ipv4_dst
+ *     or ipv6_dst based on family, smac is set to mac address of
+ *     egress device, dmac is set to nexthop mac address, rt_metric
+ *     is set to metric from route (IPv4/IPv6 only), and ifindex
+ *     is set to the device index of the nexthop from the FIB lookup.
+ *
+ *     *plen* argument is the size of the passed in struct.
+ *     *flags* argument can be a combination of one or more of the
+ *     following values:
+ *
+ *     **BPF_FIB_LOOKUP_DIRECT**
+ *             Do a direct table lookup vs full lookup using FIB
+ *             rules.
+ *     **BPF_FIB_LOOKUP_OUTPUT**
+ *             Perform lookup from an egress perspective (default is
+ *             ingress).
+ *
+ *     *ctx* is either **struct xdp_md** for XDP programs or
+ *     **struct sk_buff** tc cls_act programs.
+ *
+ * Returns
+ *     * < 0 if any input argument is invalid
+ *     *   0 on success (packet is forwarded, nexthop neighbor exists)
+ *     * > 0 one of **BPF_FIB_LKUP_RET_** codes explaining why the
+ *       packet is not forwarded or needs assist from full stack
+ *
+ *     If lookup fails with BPF_FIB_LKUP_RET_FRAG_NEEDED, then the MTU
+ *     was exceeded and output params->mtu_result contains the MTU.
+ */
+static long (*bpf_fib_lookup)(void *ctx, struct bpf_fib_lookup *params, int plen, __u32 flags) = (void *) 69;
+
+/*
+ * bpf_sock_hash_update
+ *
+ *     Add an entry to, or update a sockhash *map* referencing sockets.
+ *     The *skops* is used as a new value for the entry associated to
+ *     *key*. *flags* is one of:
+ *
+ *     **BPF_NOEXIST**
+ *             The entry for *key* must not exist in the map.
+ *     **BPF_EXIST**
+ *             The entry for *key* must already exist in the map.
+ *     **BPF_ANY**
+ *             No condition on the existence of the entry for *key*.
+ *
+ *     If the *map* has eBPF programs (parser and verdict), those will
+ *     be inherited by the socket being added. If the socket is
+ *     already attached to eBPF programs, this results in an error.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_sock_hash_update)(struct bpf_sock_ops *skops, void *map, void *key, __u64 flags) = (void *) 70;
+
+/*
+ * bpf_msg_redirect_hash
+ *
+ *     This helper is used in programs implementing policies at the
+ *     socket level. If the message *msg* is allowed to pass (i.e. if
+ *     the verdict eBPF program returns **SK_PASS**), redirect it to
+ *     the socket referenced by *map* (of type
+ *     **BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and
+ *     egress interfaces can be used for redirection. The
+ *     **BPF_F_INGRESS** value in *flags* is used to make the
+ *     distinction (ingress path is selected if the flag is present,
+ *     egress path otherwise). This is the only flag supported for now.
+ *
+ * Returns
+ *     **SK_PASS** on success, or **SK_DROP** on error.
+ */
+static long (*bpf_msg_redirect_hash)(struct sk_msg_md *msg, void *map, void *key, __u64 flags) = (void *) 71;
+
+/*
+ * bpf_sk_redirect_hash
+ *
+ *     This helper is used in programs implementing policies at the
+ *     skb socket level. If the sk_buff *skb* is allowed to pass (i.e.
+ *     if the verdict eBPF program returns **SK_PASS**), redirect it
+ *     to the socket referenced by *map* (of type
+ *     **BPF_MAP_TYPE_SOCKHASH**) using hash *key*. Both ingress and
+ *     egress interfaces can be used for redirection. The
+ *     **BPF_F_INGRESS** value in *flags* is used to make the
+ *     distinction (ingress path is selected if the flag is present,
+ *     egress otherwise). This is the only flag supported for now.
+ *
+ * Returns
+ *     **SK_PASS** on success, or **SK_DROP** on error.
+ */
+static long (*bpf_sk_redirect_hash)(struct __sk_buff *skb, void *map, void *key, __u64 flags) = (void *) 72;
+
+/*
+ * bpf_lwt_push_encap
+ *
+ *     Encapsulate the packet associated to *skb* within a Layer 3
+ *     protocol header. This header is provided in the buffer at
+ *     address *hdr*, with *len* its size in bytes. *type* indicates
+ *     the protocol of the header and can be one of:
+ *
+ *     **BPF_LWT_ENCAP_SEG6**
+ *             IPv6 encapsulation with Segment Routing Header
+ *             (**struct ipv6_sr_hdr**). *hdr* only contains the SRH,
+ *             the IPv6 header is computed by the kernel.
+ *     **BPF_LWT_ENCAP_SEG6_INLINE**
+ *             Only works if *skb* contains an IPv6 packet. Insert a
+ *             Segment Routing Header (**struct ipv6_sr_hdr**) inside
+ *             the IPv6 header.
+ *     **BPF_LWT_ENCAP_IP**
+ *             IP encapsulation (GRE/GUE/IPIP/etc). The outer header
+ *             must be IPv4 or IPv6, followed by zero or more
+ *             additional headers, up to **LWT_BPF_MAX_HEADROOM**
+ *             total bytes in all prepended headers. Please note that
+ *             if **skb_is_gso**\ (*skb*) is true, no more than two
+ *             headers can be prepended, and the inner header, if
+ *             present, should be either GRE or UDP/GUE.
+ *
+ *     **BPF_LWT_ENCAP_SEG6**\ \* types can be called by BPF programs
+ *     of type **BPF_PROG_TYPE_LWT_IN**; **BPF_LWT_ENCAP_IP** type can
+ *     be called by bpf programs of types **BPF_PROG_TYPE_LWT_IN** and
+ *     **BPF_PROG_TYPE_LWT_XMIT**.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_lwt_push_encap)(struct __sk_buff *skb, __u32 type, void *hdr, __u32 len) = (void *) 73;
+
+/*
+ * bpf_lwt_seg6_store_bytes
+ *
+ *     Store *len* bytes from address *from* into the packet
+ *     associated to *skb*, at *offset*. Only the flags, tag and TLVs
+ *     inside the outermost IPv6 Segment Routing Header can be
+ *     modified through this helper.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_lwt_seg6_store_bytes)(struct __sk_buff *skb, __u32 offset, const void *from, __u32 len) = (void *) 74;
+
+/*
+ * bpf_lwt_seg6_adjust_srh
+ *
+ *     Adjust the size allocated to TLVs in the outermost IPv6
+ *     Segment Routing Header contained in the packet associated to
+ *     *skb*, at position *offset* by *delta* bytes. Only offsets
+ *     after the segments are accepted. *delta* can be as well
+ *     positive (growing) as negative (shrinking).
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_lwt_seg6_adjust_srh)(struct __sk_buff *skb, __u32 offset, __s32 delta) = (void *) 75;
+
+/*
+ * bpf_lwt_seg6_action
+ *
+ *     Apply an IPv6 Segment Routing action of type *action* to the
+ *     packet associated to *skb*. Each action takes a parameter
+ *     contained at address *param*, and of length *param_len* bytes.
+ *     *action* can be one of:
+ *
+ *     **SEG6_LOCAL_ACTION_END_X**
+ *             End.X action: Endpoint with Layer-3 cross-connect.
+ *             Type of *param*: **struct in6_addr**.
+ *     **SEG6_LOCAL_ACTION_END_T**
+ *             End.T action: Endpoint with specific IPv6 table lookup.
+ *             Type of *param*: **int**.
+ *     **SEG6_LOCAL_ACTION_END_B6**
+ *             End.B6 action: Endpoint bound to an SRv6 policy.
+ *             Type of *param*: **struct ipv6_sr_hdr**.
+ *     **SEG6_LOCAL_ACTION_END_B6_ENCAP**
+ *             End.B6.Encap action: Endpoint bound to an SRv6
+ *             encapsulation policy.
+ *             Type of *param*: **struct ipv6_sr_hdr**.
+ *
+ *     A call to this helper is susceptible to change the underlying
+ *     packet buffer. Therefore, at load time, all checks on pointers
+ *     previously done by the verifier are invalidated and must be
+ *     performed again, if the helper is used in combination with
+ *     direct packet access.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_lwt_seg6_action)(struct __sk_buff *skb, __u32 action, void *param, __u32 param_len) = (void *) 76;
+
+/*
+ * bpf_rc_repeat
+ *
+ *     This helper is used in programs implementing IR decoding, to
+ *     report a successfully decoded repeat key message. This delays
+ *     the generation of a key up event for previously generated
+ *     key down event.
+ *
+ *     Some IR protocols like NEC have a special IR message for
+ *     repeating last button, for when a button is held down.
+ *
+ *     The *ctx* should point to the lirc sample as passed into
+ *     the program.
+ *
+ *     This helper is only available is the kernel was compiled with
+ *     the **CONFIG_BPF_LIRC_MODE2** configuration option set to
+ *     "**y**".
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_rc_repeat)(void *ctx) = (void *) 77;
+
+/*
+ * bpf_rc_keydown
+ *
+ *     This helper is used in programs implementing IR decoding, to
+ *     report a successfully decoded key press with *scancode*,
+ *     *toggle* value in the given *protocol*. The scancode will be
+ *     translated to a keycode using the rc keymap, and reported as
+ *     an input key down event. After a period a key up event is
+ *     generated. This period can be extended by calling either
+ *     **bpf_rc_keydown**\ () again with the same values, or calling
+ *     **bpf_rc_repeat**\ ().
+ *
+ *     Some protocols include a toggle bit, in case the button was
+ *     released and pressed again between consecutive scancodes.
+ *
+ *     The *ctx* should point to the lirc sample as passed into
+ *     the program.
+ *
+ *     The *protocol* is the decoded protocol number (see
+ *     **enum rc_proto** for some predefined values).
+ *
+ *     This helper is only available is the kernel was compiled with
+ *     the **CONFIG_BPF_LIRC_MODE2** configuration option set to
+ *     "**y**".
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_rc_keydown)(void *ctx, __u32 protocol, __u64 scancode, __u32 toggle) = (void *) 78;
+
+/*
+ * bpf_skb_cgroup_id
+ *
+ *     Return the cgroup v2 id of the socket associated with the *skb*.
+ *     This is roughly similar to the **bpf_get_cgroup_classid**\ ()
+ *     helper for cgroup v1 by providing a tag resp. identifier that
+ *     can be matched on or used for map lookups e.g. to implement
+ *     policy. The cgroup v2 id of a given path in the hierarchy is
+ *     exposed in user space through the f_handle API in order to get
+ *     to the same 64-bit id.
+ *
+ *     This helper can be used on TC egress path, but not on ingress,
+ *     and is available only if the kernel was compiled with the
+ *     **CONFIG_SOCK_CGROUP_DATA** configuration option.
+ *
+ * Returns
+ *     The id is returned or 0 in case the id could not be retrieved.
+ */
+static __u64 (*bpf_skb_cgroup_id)(struct __sk_buff *skb) = (void *) 79;
+
+/*
+ * bpf_get_current_cgroup_id
+ *
+ *     Get the current cgroup id based on the cgroup within which
+ *     the current task is running.
+ *
+ * Returns
+ *     A 64-bit integer containing the current cgroup id based
+ *     on the cgroup within which the current task is running.
+ */
+static __u64 (*bpf_get_current_cgroup_id)(void) = (void *) 80;
+
+/*
+ * bpf_get_local_storage
+ *
+ *     Get the pointer to the local storage area.
+ *     The type and the size of the local storage is defined
+ *     by the *map* argument.
+ *     The *flags* meaning is specific for each map type,
+ *     and has to be 0 for cgroup local storage.
+ *
+ *     Depending on the BPF program type, a local storage area
+ *     can be shared between multiple instances of the BPF program,
+ *     running simultaneously.
+ *
+ *     A user should care about the synchronization by himself.
+ *     For example, by using the **BPF_ATOMIC** instructions to alter
+ *     the shared data.
+ *
+ * Returns
+ *     A pointer to the local storage area.
+ */
+static void *(*bpf_get_local_storage)(void *map, __u64 flags) = (void *) 81;
+
+/*
+ * bpf_sk_select_reuseport
+ *
+ *     Select a **SO_REUSEPORT** socket from a
+ *     **BPF_MAP_TYPE_REUSEPORT_SOCKARRAY** *map*.
+ *     It checks the selected socket is matching the incoming
+ *     request in the socket buffer.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_sk_select_reuseport)(struct sk_reuseport_md *reuse, void *map, void *key, __u64 flags) = (void *) 82;
+
+/*
+ * bpf_skb_ancestor_cgroup_id
+ *
+ *     Return id of cgroup v2 that is ancestor of cgroup associated
+ *     with the *skb* at the *ancestor_level*.  The root cgroup is at
+ *     *ancestor_level* zero and each step down the hierarchy
+ *     increments the level. If *ancestor_level* == level of cgroup
+ *     associated with *skb*, then return value will be same as that
+ *     of **bpf_skb_cgroup_id**\ ().
+ *
+ *     The helper is useful to implement policies based on cgroups
+ *     that are upper in hierarchy than immediate cgroup associated
+ *     with *skb*.
+ *
+ *     The format of returned id and helper limitations are same as in
+ *     **bpf_skb_cgroup_id**\ ().
+ *
+ * Returns
+ *     The id is returned or 0 in case the id could not be retrieved.
+ */
+static __u64 (*bpf_skb_ancestor_cgroup_id)(struct __sk_buff *skb, int ancestor_level) = (void *) 83;
+
+/*
+ * bpf_sk_lookup_tcp
+ *
+ *     Look for TCP socket matching *tuple*, optionally in a child
+ *     network namespace *netns*. The return value must be checked,
+ *     and if non-**NULL**, released via **bpf_sk_release**\ ().
+ *
+ *     The *ctx* should point to the context of the program, such as
+ *     the skb or socket (depending on the hook in use). This is used
+ *     to determine the base network namespace for the lookup.
+ *
+ *     *tuple_size* must be one of:
+ *
+ *     **sizeof**\ (*tuple*\ **->ipv4**)
+ *             Look for an IPv4 socket.
+ *     **sizeof**\ (*tuple*\ **->ipv6**)
+ *             Look for an IPv6 socket.
+ *
+ *     If the *netns* is a negative signed 32-bit integer, then the
+ *     socket lookup table in the netns associated with the *ctx*
+ *     will be used. For the TC hooks, this is the netns of the device
+ *     in the skb. For socket hooks, this is the netns of the socket.
+ *     If *netns* is any other signed 32-bit value greater than or
+ *     equal to zero then it specifies the ID of the netns relative to
+ *     the netns associated with the *ctx*. *netns* values beyond the
+ *     range of 32-bit integers are reserved for future use.
+ *
+ *     All values for *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ *     This helper is available only if the kernel was compiled with
+ *     **CONFIG_NET** configuration option.
+ *
+ * Returns
+ *     Pointer to **struct bpf_sock**, or **NULL** in case of failure.
+ *     For sockets with reuseport option, the **struct bpf_sock**
+ *     result is from *reuse*\ **->socks**\ [] using the hash of the
+ *     tuple.
+ */
+static struct bpf_sock *(*bpf_sk_lookup_tcp)(void *ctx, struct bpf_sock_tuple *tuple, __u32 tuple_size, __u64 netns, __u64 flags) = (void *) 84;
+
+/*
+ * bpf_sk_lookup_udp
+ *
+ *     Look for UDP socket matching *tuple*, optionally in a child
+ *     network namespace *netns*. The return value must be checked,
+ *     and if non-**NULL**, released via **bpf_sk_release**\ ().
+ *
+ *     The *ctx* should point to the context of the program, such as
+ *     the skb or socket (depending on the hook in use). This is used
+ *     to determine the base network namespace for the lookup.
+ *
+ *     *tuple_size* must be one of:
+ *
+ *     **sizeof**\ (*tuple*\ **->ipv4**)
+ *             Look for an IPv4 socket.
+ *     **sizeof**\ (*tuple*\ **->ipv6**)
+ *             Look for an IPv6 socket.
+ *
+ *     If the *netns* is a negative signed 32-bit integer, then the
+ *     socket lookup table in the netns associated with the *ctx*
+ *     will be used. For the TC hooks, this is the netns of the device
+ *     in the skb. For socket hooks, this is the netns of the socket.
+ *     If *netns* is any other signed 32-bit value greater than or
+ *     equal to zero then it specifies the ID of the netns relative to
+ *     the netns associated with the *ctx*. *netns* values beyond the
+ *     range of 32-bit integers are reserved for future use.
+ *
+ *     All values for *flags* are reserved for future usage, and must
+ *     be left at zero.
+ *
+ *     This helper is available only if the kernel was compiled with
+ *     **CONFIG_NET** configuration option.
+ *
+ * Returns
+ *     Pointer to **struct bpf_sock**, or **NULL** in case of failure.
+ *     For sockets with reuseport option, the **struct bpf_sock**
+ *     result is from *reuse*\ **->socks**\ [] using the hash of the
+ *     tuple.
+ */
+static struct bpf_sock *(*bpf_sk_lookup_udp)(void *ctx, struct bpf_sock_tuple *tuple, __u32 tuple_size, __u64 netns, __u64 flags) = (void *) 85;
+
+/*
+ * bpf_sk_release
+ *
+ *     Release the reference held by *sock*. *sock* must be a
+ *     non-**NULL** pointer that was returned from
+ *     **bpf_sk_lookup_xxx**\ ().
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_sk_release)(void *sock) = (void *) 86;
+
+/*
+ * bpf_map_push_elem
+ *
+ *     Push an element *value* in *map*. *flags* is one of:
+ *
+ *     **BPF_EXIST**
+ *             If the queue/stack is full, the oldest element is
+ *             removed to make room for this.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_map_push_elem)(void *map, const void *value, __u64 flags) = (void *) 87;
+
+/*
+ * bpf_map_pop_elem
+ *
+ *     Pop an element from *map*.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_map_pop_elem)(void *map, void *value) = (void *) 88;
+
+/*
+ * bpf_map_peek_elem
+ *
+ *     Get an element from *map* without removing it.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_map_peek_elem)(void *map, void *value) = (void *) 89;
+
+/*
+ * bpf_msg_push_data
+ *
+ *     For socket policies, insert *len* bytes into *msg* at offset
+ *     *start*.
+ *
+ *     If a program of type **BPF_PROG_TYPE_SK_MSG** is run on a
+ *     *msg* it may want to insert metadata or options into the *msg*.
+ *     This can later be read and used by any of the lower layer BPF
+ *     hooks.
+ *
+ *     This helper may fail if under memory pressure (a malloc
+ *     fails) in these cases BPF programs will get an appropriate
+ *     error and BPF programs will need to handle them.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_msg_push_data)(struct sk_msg_md *msg, __u32 start, __u32 len, __u64 flags) = (void *) 90;
+
+/*
+ * bpf_msg_pop_data
+ *
+ *     Will remove *len* bytes from a *msg* starting at byte *start*.
+ *     This may result in **ENOMEM** errors under certain situations if
+ *     an allocation and copy are required due to a full ring buffer.
+ *     However, the helper will try to avoid doing the allocation
+ *     if possible. Other errors can occur if input parameters are
+ *     invalid either due to *start* byte not being valid part of *msg*
+ *     payload and/or *pop* value being to large.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_msg_pop_data)(struct sk_msg_md *msg, __u32 start, __u32 len, __u64 flags) = (void *) 91;
+
+/*
+ * bpf_rc_pointer_rel
+ *
+ *     This helper is used in programs implementing IR decoding, to
+ *     report a successfully decoded pointer movement.
+ *
+ *     The *ctx* should point to the lirc sample as passed into
+ *     the program.
+ *
+ *     This helper is only available is the kernel was compiled with
+ *     the **CONFIG_BPF_LIRC_MODE2** configuration option set to
+ *     "**y**".
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_rc_pointer_rel)(void *ctx, __s32 rel_x, __s32 rel_y) = (void *) 92;
+
+/*
+ * bpf_spin_lock
+ *
+ *     Acquire a spinlock represented by the pointer *lock*, which is
+ *     stored as part of a value of a map. Taking the lock allows to
+ *     safely update the rest of the fields in that value. The
+ *     spinlock can (and must) later be released with a call to
+ *     **bpf_spin_unlock**\ (\ *lock*\ ).
+ *
+ *     Spinlocks in BPF programs come with a number of restrictions
+ *     and constraints:
+ *
+ *     * **bpf_spin_lock** objects are only allowed inside maps of
+ *       types **BPF_MAP_TYPE_HASH** and **BPF_MAP_TYPE_ARRAY** (this
+ *       list could be extended in the future).
+ *     * BTF description of the map is mandatory.
+ *     * The BPF program can take ONE lock at a time, since taking two
+ *       or more could cause dead locks.
+ *     * Only one **struct bpf_spin_lock** is allowed per map element.
+ *     * When the lock is taken, calls (either BPF to BPF or helpers)
+ *       are not allowed.
+ *     * The **BPF_LD_ABS** and **BPF_LD_IND** instructions are not
+ *       allowed inside a spinlock-ed region.
+ *     * The BPF program MUST call **bpf_spin_unlock**\ () to release
+ *       the lock, on all execution paths, before it returns.
+ *     * The BPF program can access **struct bpf_spin_lock** only via
+ *       the **bpf_spin_lock**\ () and **bpf_spin_unlock**\ ()
+ *       helpers. Loading or storing data into the **struct
+ *       bpf_spin_lock** *lock*\ **;** field of a map is not allowed.
+ *     * To use the **bpf_spin_lock**\ () helper, the BTF description
+ *       of the map value must be a struct and have **struct
+ *       bpf_spin_lock** *anyname*\ **;** field at the top level.
+ *       Nested lock inside another struct is not allowed.
+ *     * The **struct bpf_spin_lock** *lock* field in a map value must
+ *       be aligned on a multiple of 4 bytes in that value.
+ *     * Syscall with command **BPF_MAP_LOOKUP_ELEM** does not copy
+ *       the **bpf_spin_lock** field to user space.
+ *     * Syscall with command **BPF_MAP_UPDATE_ELEM**, or update from
+ *       a BPF program, do not update the **bpf_spin_lock** field.
+ *     * **bpf_spin_lock** cannot be on the stack or inside a
+ *       networking packet (it can only be inside of a map values).
+ *     * **bpf_spin_lock** is available to root only.
+ *     * Tracing programs and socket filter programs cannot use
+ *       **bpf_spin_lock**\ () due to insufficient preemption checks
+ *       (but this may change in the future).
+ *     * **bpf_spin_lock** is not allowed in inner maps of map-in-map.
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_spin_lock)(struct bpf_spin_lock *lock) = (void *) 93;
+
+/*
+ * bpf_spin_unlock
+ *
+ *     Release the *lock* previously locked by a call to
+ *     **bpf_spin_lock**\ (\ *lock*\ ).
+ *
+ * Returns
+ *     0
+ */
+static long (*bpf_spin_unlock)(struct bpf_spin_lock *lock) = (void *) 94;
+
+/*
+ * bpf_sk_fullsock
+ *
+ *     This helper gets a **struct bpf_sock** pointer such
+ *     that all the fields in this **bpf_sock** can be accessed.
+ *
+ * Returns
+ *     A **struct bpf_sock** pointer on success, or **NULL** in
+ *     case of failure.
+ */
+static struct bpf_sock *(*bpf_sk_fullsock)(struct bpf_sock *sk) = (void *) 95;
+
+/*
+ * bpf_tcp_sock
+ *
+ *     This helper gets a **struct bpf_tcp_sock** pointer from a
+ *     **struct bpf_sock** pointer.
+ *
+ * Returns
+ *     A **struct bpf_tcp_sock** pointer on success, or **NULL** in
+ *     case of failure.
+ */
+static struct bpf_tcp_sock *(*bpf_tcp_sock)(struct bpf_sock *sk) = (void *) 96;
+
+/*
+ * bpf_skb_ecn_set_ce
+ *
+ *     Set ECN (Explicit Congestion Notification) field of IP header
+ *     to **CE** (Congestion Encountered) if current value is **ECT**
+ *     (ECN Capable Transport). Otherwise, do nothing. Works with IPv6
+ *     and IPv4.
+ *
+ * Returns
+ *     1 if the **CE** flag is set (either by the current helper call
+ *     or because it was already present), 0 if it is not set.
+ */
+static long (*bpf_skb_ecn_set_ce)(struct __sk_buff *skb) = (void *) 97;
+
+/*
+ * bpf_get_listener_sock
+ *
+ *     Return a **struct bpf_sock** pointer in **TCP_LISTEN** state.
+ *     **bpf_sk_release**\ () is unnecessary and not allowed.
+ *
+ * Returns
+ *     A **struct bpf_sock** pointer on success, or **NULL** in
+ *     case of failure.
+ */
+static struct bpf_sock *(*bpf_get_listener_sock)(struct bpf_sock *sk) = (void *) 98;
+
+/*
+ * bpf_skc_lookup_tcp
+ *
+ *     Look for TCP socket matching *tuple*, optionally in a child
+ *     network namespace *netns*. The return value must be checked,
+ *     and if non-**NULL**, released via **bpf_sk_release**\ ().
+ *
+ *     This function is identical to **bpf_sk_lookup_tcp**\ (), except
+ *     that it also returns timewait or request sockets. Use
+ *     **bpf_sk_fullsock**\ () or **bpf_tcp_sock**\ () to access the
+ *     full structure.
+ *
+ *     This helper is available only if the kernel was compiled with
+ *     **CONFIG_NET** configuration option.
+ *
+ * Returns
+ *     Pointer to **struct bpf_sock**, or **NULL** in case of failure.
+ *     For sockets with reuseport option, the **struct bpf_sock**
+ *     result is from *reuse*\ **->socks**\ [] using the hash of the
+ *     tuple.
+ */
+static struct bpf_sock *(*bpf_skc_lookup_tcp)(void *ctx, struct bpf_sock_tuple *tuple, __u32 tuple_size, __u64 netns, __u64 flags) = (void *) 99;
+
+/*
+ * bpf_tcp_check_syncookie
+ *
+ *     Check whether *iph* and *th* contain a valid SYN cookie ACK for
+ *     the listening socket in *sk*.
+ *
+ *     *iph* points to the start of the IPv4 or IPv6 header, while
+ *     *iph_len* contains **sizeof**\ (**struct iphdr**) or
+ *     **sizeof**\ (**struct ipv6hdr**).
+ *
+ *     *th* points to the start of the TCP header, while *th_len*
+ *     contains the length of the TCP header (at least
+ *     **sizeof**\ (**struct tcphdr**)).
+ *
+ * Returns
+ *     0 if *iph* and *th* are a valid SYN cookie ACK, or a negative
+ *     error otherwise.
+ */
+static long (*bpf_tcp_check_syncookie)(void *sk, void *iph, __u32 iph_len, struct tcphdr *th, __u32 th_len) = (void *) 100;
+
+/*
+ * bpf_sysctl_get_name
+ *
+ *     Get name of sysctl in /proc/sys/ and copy it into provided by
+ *     program buffer *buf* of size *buf_len*.
+ *
+ *     The buffer is always NUL terminated, unless it's zero-sized.
+ *
+ *     If *flags* is zero, full name (e.g. "net/ipv4/tcp_mem") is
+ *     copied. Use **BPF_F_SYSCTL_BASE_NAME** flag to copy base name
+ *     only (e.g. "tcp_mem").
+ *
+ * Returns
+ *     Number of character copied (not including the trailing NUL).
+ *
+ *     **-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *     truncated name in this case).
+ */
+static long (*bpf_sysctl_get_name)(struct bpf_sysctl *ctx, char *buf, unsigned long buf_len, __u64 flags) = (void *) 101;
+
+/*
+ * bpf_sysctl_get_current_value
+ *
+ *     Get current value of sysctl as it is presented in /proc/sys
+ *     (incl. newline, etc), and copy it as a string into provided
+ *     by program buffer *buf* of size *buf_len*.
+ *
+ *     The whole value is copied, no matter what file position user
+ *     space issued e.g. sys_read at.
+ *
+ *     The buffer is always NUL terminated, unless it's zero-sized.
+ *
+ * Returns
+ *     Number of character copied (not including the trailing NUL).
+ *
+ *     **-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *     truncated name in this case).
+ *
+ *     **-EINVAL** if current value was unavailable, e.g. because
+ *     sysctl is uninitialized and read returns -EIO for it.
+ */
+static long (*bpf_sysctl_get_current_value)(struct bpf_sysctl *ctx, char *buf, unsigned long buf_len) = (void *) 102;
+
+/*
+ * bpf_sysctl_get_new_value
+ *
+ *     Get new value being written by user space to sysctl (before
+ *     the actual write happens) and copy it as a string into
+ *     provided by program buffer *buf* of size *buf_len*.
+ *
+ *     User space may write new value at file position > 0.
+ *
+ *     The buffer is always NUL terminated, unless it's zero-sized.
+ *
+ * Returns
+ *     Number of character copied (not including the trailing NUL).
+ *
+ *     **-E2BIG** if the buffer wasn't big enough (*buf* will contain
+ *     truncated name in this case).
+ *
+ *     **-EINVAL** if sysctl is being read.
+ */
+static long (*bpf_sysctl_get_new_value)(struct bpf_sysctl *ctx, char *buf, unsigned long buf_len) = (void *) 103;
+
+/*
+ * bpf_sysctl_set_new_value
+ *
+ *     Override new value being written by user space to sysctl with
+ *     value provided by program in buffer *buf* of size *buf_len*.
+ *
+ *     *buf* should contain a string in same form as provided by user
+ *     space on sysctl write.
+ *
+ *     User space may write new value at file position > 0. To override
+ *     the whole sysctl value file position should be set to zero.
+ *
+ * Returns
+ *     0 on success.
+ *
+ *     **-E2BIG** if the *buf_len* is too big.
+ *
+ *     **-EINVAL** if sysctl is being read.
+ */
+static long (*bpf_sysctl_set_new_value)(struct bpf_sysctl *ctx, const char *buf, unsigned long buf_len) = (void *) 104;
+
+/*
+ * bpf_strtol
+ *
+ *     Convert the initial part of the string from buffer *buf* of
+ *     size *buf_len* to a long integer according to the given base
+ *     and save the result in *res*.
+ *
+ *     The string may begin with an arbitrary amount of white space
+ *     (as determined by **isspace**\ (3)) followed by a single
+ *     optional '**-**' sign.
+ *
+ *     Five least significant bits of *flags* encode base, other bits
+ *     are currently unused.
+ *
+ *     Base must be either 8, 10, 16 or 0 to detect it automatically
+ *     similar to user space **strtol**\ (3).
+ *
+ * Returns
+ *     Number of characters consumed on success. Must be positive but
+ *     no more than *buf_len*.
+ *
+ *     **-EINVAL** if no valid digits were found or unsupported base
+ *     was provided.
+ *
+ *     **-ERANGE** if resulting value was out of range.
+ */
+static long (*bpf_strtol)(const char *buf, unsigned long buf_len, __u64 flags, long *res) = (void *) 105;
+
+/*
+ * bpf_strtoul
+ *
+ *     Convert the initial part of the string from buffer *buf* of
+ *     size *buf_len* to an unsigned long integer according to the
+ *     given base and save the result in *res*.
+ *
+ *     The string may begin with an arbitrary amount of white space
+ *     (as determined by **isspace**\ (3)).
+ *
+ *     Five least significant bits of *flags* encode base, other bits
+ *     are currently unused.
+ *
+ *     Base must be either 8, 10, 16 or 0 to detect it automatically
+ *     similar to user space **strtoul**\ (3).
+ *
+ * Returns
+ *     Number of characters consumed on success. Must be positive but
+ *     no more than *buf_len*.
+ *
+ *     **-EINVAL** if no valid digits were found or unsupported base
+ *     was provided.
+ *
+ *     **-ERANGE** if resulting value was out of range.
+ */
+static long (*bpf_strtoul)(const char *buf, unsigned long buf_len, __u64 flags, unsigned long *res) = (void *) 106;
+
+/*
+ * bpf_sk_storage_get
+ *
+ *     Get a bpf-local-storage from a *sk*.
+ *
+ *     Logically, it could be thought of getting the value from
+ *     a *map* with *sk* as the **key**.  From this
+ *     perspective,  the usage is not much different from
+ *     **bpf_map_lookup_elem**\ (*map*, **&**\ *sk*) except this
+ *     helper enforces the key must be a full socket and the map must
+ *     be a **BPF_MAP_TYPE_SK_STORAGE** also.
+ *
+ *     Underneath, the value is stored locally at *sk* instead of
+ *     the *map*.  The *map* is used as the bpf-local-storage
+ *     "type". The bpf-local-storage "type" (i.e. the *map*) is
+ *     searched against all bpf-local-storages residing at *sk*.
+ *
+ *     *sk* is a kernel **struct sock** pointer for LSM program.
+ *     *sk* is a **struct bpf_sock** pointer for other program types.
+ *
+ *     An optional *flags* (**BPF_SK_STORAGE_GET_F_CREATE**) can be
+ *     used such that a new bpf-local-storage will be
+ *     created if one does not exist.  *value* can be used
+ *     together with **BPF_SK_STORAGE_GET_F_CREATE** to specify
+ *     the initial value of a bpf-local-storage.  If *value* is
+ *     **NULL**, the new bpf-local-storage will be zero initialized.
+ *
+ * Returns
+ *     A bpf-local-storage pointer is returned on success.
+ *
+ *     **NULL** if not found or there was an error in adding
+ *     a new bpf-local-storage.
+ */
+static void *(*bpf_sk_storage_get)(void *map, void *sk, void *value, __u64 flags) = (void *) 107;
+
+/*
+ * bpf_sk_storage_delete
+ *
+ *     Delete a bpf-local-storage from a *sk*.
+ *
+ * Returns
+ *     0 on success.
+ *
+ *     **-ENOENT** if the bpf-local-storage cannot be found.
+ *     **-EINVAL** if sk is not a fullsock (e.g. a request_sock).
+ */
+static long (*bpf_sk_storage_delete)(void *map, void *sk) = (void *) 108;
+
+/*
+ * bpf_send_signal
+ *
+ *     Send signal *sig* to the process of the current task.
+ *     The signal may be delivered to any of this process's threads.
+ *
+ * Returns
+ *     0 on success or successfully queued.
+ *
+ *     **-EBUSY** if work queue under nmi is full.
+ *
+ *     **-EINVAL** if *sig* is invalid.
+ *
+ *     **-EPERM** if no permission to send the *sig*.
+ *
+ *     **-EAGAIN** if bpf program can try again.
+ */
+static long (*bpf_send_signal)(__u32 sig) = (void *) 109;
+
+/*
+ * bpf_tcp_gen_syncookie
+ *
+ *     Try to issue a SYN cookie for the packet with corresponding
+ *     IP/TCP headers, *iph* and *th*, on the listening socket in *sk*.
+ *
+ *     *iph* points to the start of the IPv4 or IPv6 header, while
+ *     *iph_len* contains **sizeof**\ (**struct iphdr**) or
+ *     **sizeof**\ (**struct ipv6hdr**).
+ *
+ *     *th* points to the start of the TCP header, while *th_len*
+ *     contains the length of the TCP header with options (at least
+ *     **sizeof**\ (**struct tcphdr**)).
+ *
+ * Returns
+ *     On success, lower 32 bits hold the generated SYN cookie in
+ *     followed by 16 bits which hold the MSS value for that cookie,
+ *     and the top 16 bits are unused.
+ *
+ *     On failure, the returned value is one of the following:
+ *
+ *     **-EINVAL** SYN cookie cannot be issued due to error
+ *
+ *     **-ENOENT** SYN cookie should not be issued (no SYN flood)
+ *
+ *     **-EOPNOTSUPP** kernel configuration does not enable SYN cookies
+ *
+ *     **-EPROTONOSUPPORT** IP packet version is not 4 or 6
+ */
+static __s64 (*bpf_tcp_gen_syncookie)(void *sk, void *iph, __u32 iph_len, struct tcphdr *th, __u32 th_len) = (void *) 110;
+
+/*
+ * bpf_skb_output
+ *
+ *     Write raw *data* blob into a special BPF perf event held by
+ *     *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf
+ *     event must have the following attributes: **PERF_SAMPLE_RAW**
+ *     as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and
+ *     **PERF_COUNT_SW_BPF_OUTPUT** as **config**.
+ *
+ *     The *flags* are used to indicate the index in *map* for which
+ *     the value must be put, masked with **BPF_F_INDEX_MASK**.
+ *     Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU**
+ *     to indicate that the index of the current CPU core should be
+ *     used.
+ *
+ *     The value to write, of *size*, is passed through eBPF stack and
+ *     pointed by *data*.
+ *
+ *     *ctx* is a pointer to in-kernel struct sk_buff.
+ *
+ *     This helper is similar to **bpf_perf_event_output**\ () but
+ *     restricted to raw_tracepoint bpf programs.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_skb_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 111;
+
+/*
+ * bpf_probe_read_user
+ *
+ *     Safely attempt to read *size* bytes from user space address
+ *     *unsafe_ptr* and store the data in *dst*.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_probe_read_user)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 112;
+
+/*
+ * bpf_probe_read_kernel
+ *
+ *     Safely attempt to read *size* bytes from kernel space address
+ *     *unsafe_ptr* and store the data in *dst*.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_probe_read_kernel)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 113;
+
+/*
+ * bpf_probe_read_user_str
+ *
+ *     Copy a NUL terminated string from an unsafe user address
+ *     *unsafe_ptr* to *dst*. The *size* should include the
+ *     terminating NUL byte. In case the string length is smaller than
+ *     *size*, the target is not padded with further NUL bytes. If the
+ *     string length is larger than *size*, just *size*-1 bytes are
+ *     copied and the last byte is set to NUL.
+ *
+ *     On success, returns the number of bytes that were written,
+ *     including the terminal NUL. This makes this helper useful in
+ *     tracing programs for reading strings, and more importantly to
+ *     get its length at runtime. See the following snippet:
+ *
+ *     ::
+ *
+ *             SEC("kprobe/sys_open")
+ *             void bpf_sys_open(struct pt_regs *ctx)
+ *             {
+ *                     char buf[PATHLEN]; // PATHLEN is defined to 256
+ *                     int res = bpf_probe_read_user_str(buf, sizeof(buf),
+ *                                                       ctx->di);
+ *
+ *                     // Consume buf, for example push it to
+ *                     // userspace via bpf_perf_event_output(); we
+ *                     // can use res (the string length) as event
+ *                     // size, after checking its boundaries.
+ *             }
+ *
+ *     In comparison, using **bpf_probe_read_user**\ () helper here
+ *     instead to read the string would require to estimate the length
+ *     at compile time, and would often result in copying more memory
+ *     than necessary.
+ *
+ *     Another useful use case is when parsing individual process
+ *     arguments or individual environment variables navigating
+ *     *current*\ **->mm->arg_start** and *current*\
+ *     **->mm->env_start**: using this helper and the return value,
+ *     one can quickly iterate at the right offset of the memory area.
+ *
+ * Returns
+ *     On success, the strictly positive length of the output string,
+ *     including the trailing NUL character. On error, a negative
+ *     value.
+ */
+static long (*bpf_probe_read_user_str)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 114;
+
+/*
+ * bpf_probe_read_kernel_str
+ *
+ *     Copy a NUL terminated string from an unsafe kernel address *unsafe_ptr*
+ *     to *dst*. Same semantics as with **bpf_probe_read_user_str**\ () apply.
+ *
+ * Returns
+ *     On success, the strictly positive length of the string, including
+ *     the trailing NUL character. On error, a negative value.
+ */
+static long (*bpf_probe_read_kernel_str)(void *dst, __u32 size, const void *unsafe_ptr) = (void *) 115;
+
+/*
+ * bpf_tcp_send_ack
+ *
+ *     Send out a tcp-ack. *tp* is the in-kernel struct **tcp_sock**.
+ *     *rcv_nxt* is the ack_seq to be sent out.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_tcp_send_ack)(void *tp, __u32 rcv_nxt) = (void *) 116;
+
+/*
+ * bpf_send_signal_thread
+ *
+ *     Send signal *sig* to the thread corresponding to the current task.
+ *
+ * Returns
+ *     0 on success or successfully queued.
+ *
+ *     **-EBUSY** if work queue under nmi is full.
+ *
+ *     **-EINVAL** if *sig* is invalid.
+ *
+ *     **-EPERM** if no permission to send the *sig*.
+ *
+ *     **-EAGAIN** if bpf program can try again.
+ */
+static long (*bpf_send_signal_thread)(__u32 sig) = (void *) 117;
+
+/*
+ * bpf_jiffies64
+ *
+ *     Obtain the 64bit jiffies
+ *
+ * Returns
+ *     The 64 bit jiffies
+ */
+static __u64 (*bpf_jiffies64)(void) = (void *) 118;
+
+/*
+ * bpf_read_branch_records
+ *
+ *     For an eBPF program attached to a perf event, retrieve the
+ *     branch records (**struct perf_branch_entry**) associated to *ctx*
+ *     and store it in the buffer pointed by *buf* up to size
+ *     *size* bytes.
+ *
+ * Returns
+ *     On success, number of bytes written to *buf*. On error, a
+ *     negative value.
+ *
+ *     The *flags* can be set to **BPF_F_GET_BRANCH_RECORDS_SIZE** to
+ *     instead return the number of bytes required to store all the
+ *     branch entries. If this flag is set, *buf* may be NULL.
+ *
+ *     **-EINVAL** if arguments invalid or **size** not a multiple
+ *     of **sizeof**\ (**struct perf_branch_entry**\ ).
+ *
+ *     **-ENOENT** if architecture does not support branch records.
+ */
+static long (*bpf_read_branch_records)(struct bpf_perf_event_data *ctx, void *buf, __u32 size, __u64 flags) = (void *) 119;
+
+/*
+ * bpf_get_ns_current_pid_tgid
+ *
+ *     Returns 0 on success, values for *pid* and *tgid* as seen from the current
+ *     *namespace* will be returned in *nsdata*.
+ *
+ * Returns
+ *     0 on success, or one of the following in case of failure:
+ *
+ *     **-EINVAL** if dev and inum supplied don't match dev_t and inode number
+ *     with nsfs of current task, or if dev conversion to dev_t lost high bits.
+ *
+ *     **-ENOENT** if pidns does not exists for the current task.
+ */
+static long (*bpf_get_ns_current_pid_tgid)(__u64 dev, __u64 ino, struct bpf_pidns_info *nsdata, __u32 size) = (void *) 120;
+
+/*
+ * bpf_xdp_output
+ *
+ *     Write raw *data* blob into a special BPF perf event held by
+ *     *map* of type **BPF_MAP_TYPE_PERF_EVENT_ARRAY**. This perf
+ *     event must have the following attributes: **PERF_SAMPLE_RAW**
+ *     as **sample_type**, **PERF_TYPE_SOFTWARE** as **type**, and
+ *     **PERF_COUNT_SW_BPF_OUTPUT** as **config**.
+ *
+ *     The *flags* are used to indicate the index in *map* for which
+ *     the value must be put, masked with **BPF_F_INDEX_MASK**.
+ *     Alternatively, *flags* can be set to **BPF_F_CURRENT_CPU**
+ *     to indicate that the index of the current CPU core should be
+ *     used.
+ *
+ *     The value to write, of *size*, is passed through eBPF stack and
+ *     pointed by *data*.
+ *
+ *     *ctx* is a pointer to in-kernel struct xdp_buff.
+ *
+ *     This helper is similar to **bpf_perf_eventoutput**\ () but
+ *     restricted to raw_tracepoint bpf programs.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_xdp_output)(void *ctx, void *map, __u64 flags, void *data, __u64 size) = (void *) 121;
+
+/*
+ * bpf_get_netns_cookie
+ *
+ *     Retrieve the cookie (generated by the kernel) of the network
+ *     namespace the input *ctx* is associated with. The network
+ *     namespace cookie remains stable for its lifetime and provides
+ *     a global identifier that can be assumed unique. If *ctx* is
+ *     NULL, then the helper returns the cookie for the initial
+ *     network namespace. The cookie itself is very similar to that
+ *     of **bpf_get_socket_cookie**\ () helper, but for network
+ *     namespaces instead of sockets.
+ *
+ * Returns
+ *     A 8-byte long opaque number.
+ */
+static __u64 (*bpf_get_netns_cookie)(void *ctx) = (void *) 122;
+
+/*
+ * bpf_get_current_ancestor_cgroup_id
+ *
+ *     Return id of cgroup v2 that is ancestor of the cgroup associated
+ *     with the current task at the *ancestor_level*. The root cgroup
+ *     is at *ancestor_level* zero and each step down the hierarchy
+ *     increments the level. If *ancestor_level* == level of cgroup
+ *     associated with the current task, then return value will be the
+ *     same as that of **bpf_get_current_cgroup_id**\ ().
+ *
+ *     The helper is useful to implement policies based on cgroups
+ *     that are upper in hierarchy than immediate cgroup associated
+ *     with the current task.
+ *
+ *     The format of returned id and helper limitations are same as in
+ *     **bpf_get_current_cgroup_id**\ ().
+ *
+ * Returns
+ *     The id is returned or 0 in case the id could not be retrieved.
+ */
+static __u64 (*bpf_get_current_ancestor_cgroup_id)(int ancestor_level) = (void *) 123;
+
+/*
+ * bpf_sk_assign
+ *
+ *     Helper is overloaded depending on BPF program type. This
+ *     description applies to **BPF_PROG_TYPE_SCHED_CLS** and
+ *     **BPF_PROG_TYPE_SCHED_ACT** programs.
+ *
+ *     Assign the *sk* to the *skb*. When combined with appropriate
+ *     routing configuration to receive the packet towards the socket,
+ *     will cause *skb* to be delivered to the specified socket.
+ *     Subsequent redirection of *skb* via  **bpf_redirect**\ (),
+ *     **bpf_clone_redirect**\ () or other methods outside of BPF may
+ *     interfere with successful delivery to the socket.
+ *
+ *     This operation is only valid from TC ingress path.
+ *
+ *     The *flags* argument must be zero.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure:
+ *
+ *     **-EINVAL** if specified *flags* are not supported.
+ *
+ *     **-ENOENT** if the socket is unavailable for assignment.
+ *
+ *     **-ENETUNREACH** if the socket is unreachable (wrong netns).
+ *
+ *     **-EOPNOTSUPP** if the operation is not supported, for example
+ *     a call from outside of TC ingress.
+ *
+ *     **-ESOCKTNOSUPPORT** if the socket type is not supported
+ *     (reuseport).
+ */
+static long (*bpf_sk_assign)(void *ctx, void *sk, __u64 flags) = (void *) 124;
+
+/*
+ * bpf_ktime_get_boot_ns
+ *
+ *     Return the time elapsed since system boot, in nanoseconds.
+ *     Does include the time the system was suspended.
+ *     See: **clock_gettime**\ (**CLOCK_BOOTTIME**)
+ *
+ * Returns
+ *     Current *ktime*.
+ */
+static __u64 (*bpf_ktime_get_boot_ns)(void) = (void *) 125;
+
+/*
+ * bpf_seq_printf
+ *
+ *     **bpf_seq_printf**\ () uses seq_file **seq_printf**\ () to print
+ *     out the format string.
+ *     The *m* represents the seq_file. The *fmt* and *fmt_size* are for
+ *     the format string itself. The *data* and *data_len* are format string
+ *     arguments. The *data* are a **u64** array and corresponding format string
+ *     values are stored in the array. For strings and pointers where pointees
+ *     are accessed, only the pointer values are stored in the *data* array.
+ *     The *data_len* is the size of *data* in bytes - must be a multiple of 8.
+ *
+ *     Formats **%s**, **%p{i,I}{4,6}** requires to read kernel memory.
+ *     Reading kernel memory may fail due to either invalid address or
+ *     valid address but requiring a major memory fault. If reading kernel memory
+ *     fails, the string for **%s** will be an empty string, and the ip
+ *     address for **%p{i,I}{4,6}** will be 0. Not returning error to
+ *     bpf program is consistent with what **bpf_trace_printk**\ () does for now.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure:
+ *
+ *     **-EBUSY** if per-CPU memory copy buffer is busy, can try again
+ *     by returning 1 from bpf program.
+ *
+ *     **-EINVAL** if arguments are invalid, or if *fmt* is invalid/unsupported.
+ *
+ *     **-E2BIG** if *fmt* contains too many format specifiers.
+ *
+ *     **-EOVERFLOW** if an overflow happened: The same object will be tried again.
+ */
+static long (*bpf_seq_printf)(struct seq_file *m, const char *fmt, __u32 fmt_size, const void *data, __u32 data_len) = (void *) 126;
+
+/*
+ * bpf_seq_write
+ *
+ *     **bpf_seq_write**\ () uses seq_file **seq_write**\ () to write the data.
+ *     The *m* represents the seq_file. The *data* and *len* represent the
+ *     data to write in bytes.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure:
+ *
+ *     **-EOVERFLOW** if an overflow happened: The same object will be tried again.
+ */
+static long (*bpf_seq_write)(struct seq_file *m, const void *data, __u32 len) = (void *) 127;
+
+/*
+ * bpf_sk_cgroup_id
+ *
+ *     Return the cgroup v2 id of the socket *sk*.
+ *
+ *     *sk* must be a non-**NULL** pointer to a socket, e.g. one
+ *     returned from **bpf_sk_lookup_xxx**\ (),
+ *     **bpf_sk_fullsock**\ (), etc. The format of returned id is
+ *     same as in **bpf_skb_cgroup_id**\ ().
+ *
+ *     This helper is available only if the kernel was compiled with
+ *     the **CONFIG_SOCK_CGROUP_DATA** configuration option.
+ *
+ * Returns
+ *     The id is returned or 0 in case the id could not be retrieved.
+ */
+static __u64 (*bpf_sk_cgroup_id)(void *sk) = (void *) 128;
+
+/*
+ * bpf_sk_ancestor_cgroup_id
+ *
+ *     Return id of cgroup v2 that is ancestor of cgroup associated
+ *     with the *sk* at the *ancestor_level*.  The root cgroup is at
+ *     *ancestor_level* zero and each step down the hierarchy
+ *     increments the level. If *ancestor_level* == level of cgroup
+ *     associated with *sk*, then return value will be same as that
+ *     of **bpf_sk_cgroup_id**\ ().
+ *
+ *     The helper is useful to implement policies based on cgroups
+ *     that are upper in hierarchy than immediate cgroup associated
+ *     with *sk*.
+ *
+ *     The format of returned id and helper limitations are same as in
+ *     **bpf_sk_cgroup_id**\ ().
+ *
+ * Returns
+ *     The id is returned or 0 in case the id could not be retrieved.
+ */
+static __u64 (*bpf_sk_ancestor_cgroup_id)(void *sk, int ancestor_level) = (void *) 129;
+
+/*
+ * bpf_ringbuf_output
+ *
+ *     Copy *size* bytes from *data* into a ring buffer *ringbuf*.
+ *     If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification
+ *     of new data availability is sent.
+ *     If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification
+ *     of new data availability is sent unconditionally.
+ *     If **0** is specified in *flags*, an adaptive notification
+ *     of new data availability is sent.
+ *
+ *     An adaptive notification is a notification sent whenever the user-space
+ *     process has caught up and consumed all available payloads. In case the user-space
+ *     process is still processing a previous payload, then no notification is needed
+ *     as it will process the newly added payload automatically.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_ringbuf_output)(void *ringbuf, void *data, __u64 size, __u64 flags) = (void *) 130;
+
+/*
+ * bpf_ringbuf_reserve
+ *
+ *     Reserve *size* bytes of payload in a ring buffer *ringbuf*.
+ *     *flags* must be 0.
+ *
+ * Returns
+ *     Valid pointer with *size* bytes of memory available; NULL,
+ *     otherwise.
+ */
+static void *(*bpf_ringbuf_reserve)(void *ringbuf, __u64 size, __u64 flags) = (void *) 131;
+
+/*
+ * bpf_ringbuf_submit
+ *
+ *     Submit reserved ring buffer sample, pointed to by *data*.
+ *     If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification
+ *     of new data availability is sent.
+ *     If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification
+ *     of new data availability is sent unconditionally.
+ *     If **0** is specified in *flags*, an adaptive notification
+ *     of new data availability is sent.
+ *
+ *     See 'bpf_ringbuf_output()' for the definition of adaptive notification.
+ *
+ * Returns
+ *     Nothing. Always succeeds.
+ */
+static void (*bpf_ringbuf_submit)(void *data, __u64 flags) = (void *) 132;
+
+/*
+ * bpf_ringbuf_discard
+ *
+ *     Discard reserved ring buffer sample, pointed to by *data*.
+ *     If **BPF_RB_NO_WAKEUP** is specified in *flags*, no notification
+ *     of new data availability is sent.
+ *     If **BPF_RB_FORCE_WAKEUP** is specified in *flags*, notification
+ *     of new data availability is sent unconditionally.
+ *     If **0** is specified in *flags*, an adaptive notification
+ *     of new data availability is sent.
+ *
+ *     See 'bpf_ringbuf_output()' for the definition of adaptive notification.
+ *
+ * Returns
+ *     Nothing. Always succeeds.
+ */
+static void (*bpf_ringbuf_discard)(void *data, __u64 flags) = (void *) 133;
+
+/*
+ * bpf_ringbuf_query
+ *
+ *     Query various characteristics of provided ring buffer. What
+ *     exactly is queries is determined by *flags*:
+ *
+ *     * **BPF_RB_AVAIL_DATA**: Amount of data not yet consumed.
+ *     * **BPF_RB_RING_SIZE**: The size of ring buffer.
+ *     * **BPF_RB_CONS_POS**: Consumer position (can wrap around).
+ *     * **BPF_RB_PROD_POS**: Producer(s) position (can wrap around).
+ *
+ *     Data returned is just a momentary snapshot of actual values
+ *     and could be inaccurate, so this facility should be used to
+ *     power heuristics and for reporting, not to make 100% correct
+ *     calculation.
+ *
+ * Returns
+ *     Requested value, or 0, if *flags* are not recognized.
+ */
+static __u64 (*bpf_ringbuf_query)(void *ringbuf, __u64 flags) = (void *) 134;
+
+/*
+ * bpf_csum_level
+ *
+ *     Change the skbs checksum level by one layer up or down, or
+ *     reset it entirely to none in order to have the stack perform
+ *     checksum validation. The level is applicable to the following
+ *     protocols: TCP, UDP, GRE, SCTP, FCOE. For example, a decap of
+ *     | ETH | IP | UDP | GUE | IP | TCP | into | ETH | IP | TCP |
+ *     through **bpf_skb_adjust_room**\ () helper with passing in
+ *     **BPF_F_ADJ_ROOM_NO_CSUM_RESET** flag would require one call
+ *     to **bpf_csum_level**\ () with **BPF_CSUM_LEVEL_DEC** since
+ *     the UDP header is removed. Similarly, an encap of the latter
+ *     into the former could be accompanied by a helper call to
+ *     **bpf_csum_level**\ () with **BPF_CSUM_LEVEL_INC** if the
+ *     skb is still intended to be processed in higher layers of the
+ *     stack instead of just egressing at tc.
+ *
+ *     There are three supported level settings at this time:
+ *
+ *     * **BPF_CSUM_LEVEL_INC**: Increases skb->csum_level for skbs
+ *       with CHECKSUM_UNNECESSARY.
+ *     * **BPF_CSUM_LEVEL_DEC**: Decreases skb->csum_level for skbs
+ *       with CHECKSUM_UNNECESSARY.
+ *     * **BPF_CSUM_LEVEL_RESET**: Resets skb->csum_level to 0 and
+ *       sets CHECKSUM_NONE to force checksum validation by the stack.
+ *     * **BPF_CSUM_LEVEL_QUERY**: No-op, returns the current
+ *       skb->csum_level.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure. In the
+ *     case of **BPF_CSUM_LEVEL_QUERY**, the current skb->csum_level
+ *     is returned or the error code -EACCES in case the skb is not
+ *     subject to CHECKSUM_UNNECESSARY.
+ */
+static long (*bpf_csum_level)(struct __sk_buff *skb, __u64 level) = (void *) 135;
+
+/*
+ * bpf_skc_to_tcp6_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *tcp6_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct tcp6_sock *(*bpf_skc_to_tcp6_sock)(void *sk) = (void *) 136;
+
+/*
+ * bpf_skc_to_tcp_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *tcp_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct tcp_sock *(*bpf_skc_to_tcp_sock)(void *sk) = (void *) 137;
+
+/*
+ * bpf_skc_to_tcp_timewait_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *tcp_timewait_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct tcp_timewait_sock *(*bpf_skc_to_tcp_timewait_sock)(void *sk) = (void *) 138;
+
+/*
+ * bpf_skc_to_tcp_request_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *tcp_request_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct tcp_request_sock *(*bpf_skc_to_tcp_request_sock)(void *sk) = (void *) 139;
+
+/*
+ * bpf_skc_to_udp6_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *udp6_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct udp6_sock *(*bpf_skc_to_udp6_sock)(void *sk) = (void *) 140;
+
+/*
+ * bpf_get_task_stack
+ *
+ *     Return a user or a kernel stack in bpf program provided buffer.
+ *     To achieve this, the helper needs *task*, which is a valid
+ *     pointer to **struct task_struct**. To store the stacktrace, the
+ *     bpf program provides *buf* with a nonnegative *size*.
+ *
+ *     The last argument, *flags*, holds the number of stack frames to
+ *     skip (from 0 to 255), masked with
+ *     **BPF_F_SKIP_FIELD_MASK**. The next bits can be used to set
+ *     the following flags:
+ *
+ *     **BPF_F_USER_STACK**
+ *             Collect a user space stack instead of a kernel stack.
+ *     **BPF_F_USER_BUILD_ID**
+ *             Collect buildid+offset instead of ips for user stack,
+ *             only valid if **BPF_F_USER_STACK** is also specified.
+ *
+ *     **bpf_get_task_stack**\ () can collect up to
+ *     **PERF_MAX_STACK_DEPTH** both kernel and user frames, subject
+ *     to sufficient large buffer size. Note that
+ *     this limit can be controlled with the **sysctl** program, and
+ *     that it should be manually increased in order to profile long
+ *     user stacks (such as stacks for Java programs). To do so, use:
+ *
+ *     ::
+ *
+ *             # sysctl kernel.perf_event_max_stack=<new value>
+ *
+ * Returns
+ *     The non-negative copied *buf* length equal to or less than
+ *     *size* on success, or a negative error in case of failure.
+ */
+static long (*bpf_get_task_stack)(struct task_struct *task, void *buf, __u32 size, __u64 flags) = (void *) 141;
+
+/*
+ * bpf_load_hdr_opt
+ *
+ *     Load header option.  Support reading a particular TCP header
+ *     option for bpf program (**BPF_PROG_TYPE_SOCK_OPS**).
+ *
+ *     If *flags* is 0, it will search the option from the
+ *     *skops*\ **->skb_data**.  The comment in **struct bpf_sock_ops**
+ *     has details on what skb_data contains under different
+ *     *skops*\ **->op**.
+ *
+ *     The first byte of the *searchby_res* specifies the
+ *     kind that it wants to search.
+ *
+ *     If the searching kind is an experimental kind
+ *     (i.e. 253 or 254 according to RFC6994).  It also
+ *     needs to specify the "magic" which is either
+ *     2 bytes or 4 bytes.  It then also needs to
+ *     specify the size of the magic by using
+ *     the 2nd byte which is "kind-length" of a TCP
+ *     header option and the "kind-length" also
+ *     includes the first 2 bytes "kind" and "kind-length"
+ *     itself as a normal TCP header option also does.
+ *
+ *     For example, to search experimental kind 254 with
+ *     2 byte magic 0xeB9F, the searchby_res should be
+ *     [ 254, 4, 0xeB, 0x9F, 0, 0, .... 0 ].
+ *
+ *     To search for the standard window scale option (3),
+ *     the *searchby_res* should be [ 3, 0, 0, .... 0 ].
+ *     Note, kind-length must be 0 for regular option.
+ *
+ *     Searching for No-Op (0) and End-of-Option-List (1) are
+ *     not supported.
+ *
+ *     *len* must be at least 2 bytes which is the minimal size
+ *     of a header option.
+ *
+ *     Supported flags:
+ *
+ *     * **BPF_LOAD_HDR_OPT_TCP_SYN** to search from the
+ *       saved_syn packet or the just-received syn packet.
+ *
+ *
+ * Returns
+ *     > 0 when found, the header option is copied to *searchby_res*.
+ *     The return value is the total length copied. On failure, a
+ *     negative error code is returned:
+ *
+ *     **-EINVAL** if a parameter is invalid.
+ *
+ *     **-ENOMSG** if the option is not found.
+ *
+ *     **-ENOENT** if no syn packet is available when
+ *     **BPF_LOAD_HDR_OPT_TCP_SYN** is used.
+ *
+ *     **-ENOSPC** if there is not enough space.  Only *len* number of
+ *     bytes are copied.
+ *
+ *     **-EFAULT** on failure to parse the header options in the
+ *     packet.
+ *
+ *     **-EPERM** if the helper cannot be used under the current
+ *     *skops*\ **->op**.
+ */
+static long (*bpf_load_hdr_opt)(struct bpf_sock_ops *skops, void *searchby_res, __u32 len, __u64 flags) = (void *) 142;
+
+/*
+ * bpf_store_hdr_opt
+ *
+ *     Store header option.  The data will be copied
+ *     from buffer *from* with length *len* to the TCP header.
+ *
+ *     The buffer *from* should have the whole option that
+ *     includes the kind, kind-length, and the actual
+ *     option data.  The *len* must be at least kind-length
+ *     long.  The kind-length does not have to be 4 byte
+ *     aligned.  The kernel will take care of the padding
+ *     and setting the 4 bytes aligned value to th->doff.
+ *
+ *     This helper will check for duplicated option
+ *     by searching the same option in the outgoing skb.
+ *
+ *     This helper can only be called during
+ *     **BPF_SOCK_OPS_WRITE_HDR_OPT_CB**.
+ *
+ *
+ * Returns
+ *     0 on success, or negative error in case of failure:
+ *
+ *     **-EINVAL** If param is invalid.
+ *
+ *     **-ENOSPC** if there is not enough space in the header.
+ *     Nothing has been written
+ *
+ *     **-EEXIST** if the option already exists.
+ *
+ *     **-EFAULT** on failrue to parse the existing header options.
+ *
+ *     **-EPERM** if the helper cannot be used under the current
+ *     *skops*\ **->op**.
+ */
+static long (*bpf_store_hdr_opt)(struct bpf_sock_ops *skops, const void *from, __u32 len, __u64 flags) = (void *) 143;
+
+/*
+ * bpf_reserve_hdr_opt
+ *
+ *     Reserve *len* bytes for the bpf header option.  The
+ *     space will be used by **bpf_store_hdr_opt**\ () later in
+ *     **BPF_SOCK_OPS_WRITE_HDR_OPT_CB**.
+ *
+ *     If **bpf_reserve_hdr_opt**\ () is called multiple times,
+ *     the total number of bytes will be reserved.
+ *
+ *     This helper can only be called during
+ *     **BPF_SOCK_OPS_HDR_OPT_LEN_CB**.
+ *
+ *
+ * Returns
+ *     0 on success, or negative error in case of failure:
+ *
+ *     **-EINVAL** if a parameter is invalid.
+ *
+ *     **-ENOSPC** if there is not enough space in the header.
+ *
+ *     **-EPERM** if the helper cannot be used under the current
+ *     *skops*\ **->op**.
+ */
+static long (*bpf_reserve_hdr_opt)(struct bpf_sock_ops *skops, __u32 len, __u64 flags) = (void *) 144;
+
+/*
+ * bpf_inode_storage_get
+ *
+ *     Get a bpf_local_storage from an *inode*.
+ *
+ *     Logically, it could be thought of as getting the value from
+ *     a *map* with *inode* as the **key**.  From this
+ *     perspective,  the usage is not much different from
+ *     **bpf_map_lookup_elem**\ (*map*, **&**\ *inode*) except this
+ *     helper enforces the key must be an inode and the map must also
+ *     be a **BPF_MAP_TYPE_INODE_STORAGE**.
+ *
+ *     Underneath, the value is stored locally at *inode* instead of
+ *     the *map*.  The *map* is used as the bpf-local-storage
+ *     "type". The bpf-local-storage "type" (i.e. the *map*) is
+ *     searched against all bpf_local_storage residing at *inode*.
+ *
+ *     An optional *flags* (**BPF_LOCAL_STORAGE_GET_F_CREATE**) can be
+ *     used such that a new bpf_local_storage will be
+ *     created if one does not exist.  *value* can be used
+ *     together with **BPF_LOCAL_STORAGE_GET_F_CREATE** to specify
+ *     the initial value of a bpf_local_storage.  If *value* is
+ *     **NULL**, the new bpf_local_storage will be zero initialized.
+ *
+ * Returns
+ *     A bpf_local_storage pointer is returned on success.
+ *
+ *     **NULL** if not found or there was an error in adding
+ *     a new bpf_local_storage.
+ */
+static void *(*bpf_inode_storage_get)(void *map, void *inode, void *value, __u64 flags) = (void *) 145;
+
+/*
+ * bpf_inode_storage_delete
+ *
+ *     Delete a bpf_local_storage from an *inode*.
+ *
+ * Returns
+ *     0 on success.
+ *
+ *     **-ENOENT** if the bpf_local_storage cannot be found.
+ */
+static int (*bpf_inode_storage_delete)(void *map, void *inode) = (void *) 146;
+
+/*
+ * bpf_d_path
+ *
+ *     Return full path for given **struct path** object, which
+ *     needs to be the kernel BTF *path* object. The path is
+ *     returned in the provided buffer *buf* of size *sz* and
+ *     is zero terminated.
+ *
+ *
+ * Returns
+ *     On success, the strictly positive length of the string,
+ *     including the trailing NUL character. On error, a negative
+ *     value.
+ */
+static long (*bpf_d_path)(struct path *path, char *buf, __u32 sz) = (void *) 147;
+
+/*
+ * bpf_copy_from_user
+ *
+ *     Read *size* bytes from user space address *user_ptr* and store
+ *     the data in *dst*. This is a wrapper of **copy_from_user**\ ().
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_copy_from_user)(void *dst, __u32 size, const void *user_ptr) = (void *) 148;
+
+/*
+ * bpf_snprintf_btf
+ *
+ *     Use BTF to store a string representation of *ptr*->ptr in *str*,
+ *     using *ptr*->type_id.  This value should specify the type
+ *     that *ptr*->ptr points to. LLVM __builtin_btf_type_id(type, 1)
+ *     can be used to look up vmlinux BTF type ids. Traversing the
+ *     data structure using BTF, the type information and values are
+ *     stored in the first *str_size* - 1 bytes of *str*.  Safe copy of
+ *     the pointer data is carried out to avoid kernel crashes during
+ *     operation.  Smaller types can use string space on the stack;
+ *     larger programs can use map data to store the string
+ *     representation.
+ *
+ *     The string can be subsequently shared with userspace via
+ *     bpf_perf_event_output() or ring buffer interfaces.
+ *     bpf_trace_printk() is to be avoided as it places too small
+ *     a limit on string size to be useful.
+ *
+ *     *flags* is a combination of
+ *
+ *     **BTF_F_COMPACT**
+ *             no formatting around type information
+ *     **BTF_F_NONAME**
+ *             no struct/union member names/types
+ *     **BTF_F_PTR_RAW**
+ *             show raw (unobfuscated) pointer values;
+ *             equivalent to printk specifier %px.
+ *     **BTF_F_ZERO**
+ *             show zero-valued struct/union members; they
+ *             are not displayed by default
+ *
+ *
+ * Returns
+ *     The number of bytes that were written (or would have been
+ *     written if output had to be truncated due to string size),
+ *     or a negative error in cases of failure.
+ */
+static long (*bpf_snprintf_btf)(char *str, __u32 str_size, struct btf_ptr *ptr, __u32 btf_ptr_size, __u64 flags) = (void *) 149;
+
+/*
+ * bpf_seq_printf_btf
+ *
+ *     Use BTF to write to seq_write a string representation of
+ *     *ptr*->ptr, using *ptr*->type_id as per bpf_snprintf_btf().
+ *     *flags* are identical to those used for bpf_snprintf_btf.
+ *
+ * Returns
+ *     0 on success or a negative error in case of failure.
+ */
+static long (*bpf_seq_printf_btf)(struct seq_file *m, struct btf_ptr *ptr, __u32 ptr_size, __u64 flags) = (void *) 150;
+
+/*
+ * bpf_skb_cgroup_classid
+ *
+ *     See **bpf_get_cgroup_classid**\ () for the main description.
+ *     This helper differs from **bpf_get_cgroup_classid**\ () in that
+ *     the cgroup v1 net_cls class is retrieved only from the *skb*'s
+ *     associated socket instead of the current process.
+ *
+ * Returns
+ *     The id is returned or 0 in case the id could not be retrieved.
+ */
+static __u64 (*bpf_skb_cgroup_classid)(struct __sk_buff *skb) = (void *) 151;
+
+/*
+ * bpf_redirect_neigh
+ *
+ *     Redirect the packet to another net device of index *ifindex*
+ *     and fill in L2 addresses from neighboring subsystem. This helper
+ *     is somewhat similar to **bpf_redirect**\ (), except that it
+ *     populates L2 addresses as well, meaning, internally, the helper
+ *     relies on the neighbor lookup for the L2 address of the nexthop.
+ *
+ *     The helper will perform a FIB lookup based on the skb's
+ *     networking header to get the address of the next hop, unless
+ *     this is supplied by the caller in the *params* argument. The
+ *     *plen* argument indicates the len of *params* and should be set
+ *     to 0 if *params* is NULL.
+ *
+ *     The *flags* argument is reserved and must be 0. The helper is
+ *     currently only supported for tc BPF program types, and enabled
+ *     for IPv4 and IPv6 protocols.
+ *
+ * Returns
+ *     The helper returns **TC_ACT_REDIRECT** on success or
+ *     **TC_ACT_SHOT** on error.
+ */
+static long (*bpf_redirect_neigh)(__u32 ifindex, struct bpf_redir_neigh *params, int plen, __u64 flags) = (void *) 152;
+
+/*
+ * bpf_per_cpu_ptr
+ *
+ *     Take a pointer to a percpu ksym, *percpu_ptr*, and return a
+ *     pointer to the percpu kernel variable on *cpu*. A ksym is an
+ *     extern variable decorated with '__ksym'. For ksym, there is a
+ *     global var (either static or global) defined of the same name
+ *     in the kernel. The ksym is percpu if the global var is percpu.
+ *     The returned pointer points to the global percpu var on *cpu*.
+ *
+ *     bpf_per_cpu_ptr() has the same semantic as per_cpu_ptr() in the
+ *     kernel, except that bpf_per_cpu_ptr() may return NULL. This
+ *     happens if *cpu* is larger than nr_cpu_ids. The caller of
+ *     bpf_per_cpu_ptr() must check the returned value.
+ *
+ * Returns
+ *     A pointer pointing to the kernel percpu variable on *cpu*, or
+ *     NULL, if *cpu* is invalid.
+ */
+static void *(*bpf_per_cpu_ptr)(const void *percpu_ptr, __u32 cpu) = (void *) 153;
+
+/*
+ * bpf_this_cpu_ptr
+ *
+ *     Take a pointer to a percpu ksym, *percpu_ptr*, and return a
+ *     pointer to the percpu kernel variable on this cpu. See the
+ *     description of 'ksym' in **bpf_per_cpu_ptr**\ ().
+ *
+ *     bpf_this_cpu_ptr() has the same semantic as this_cpu_ptr() in
+ *     the kernel. Different from **bpf_per_cpu_ptr**\ (), it would
+ *     never return NULL.
+ *
+ * Returns
+ *     A pointer pointing to the kernel percpu variable on this cpu.
+ */
+static void *(*bpf_this_cpu_ptr)(const void *percpu_ptr) = (void *) 154;
+
+/*
+ * bpf_redirect_peer
+ *
+ *     Redirect the packet to another net device of index *ifindex*.
+ *     This helper is somewhat similar to **bpf_redirect**\ (), except
+ *     that the redirection happens to the *ifindex*' peer device and
+ *     the netns switch takes place from ingress to ingress without
+ *     going through the CPU's backlog queue.
+ *
+ *     The *flags* argument is reserved and must be 0. The helper is
+ *     currently only supported for tc BPF program types at the ingress
+ *     hook and for veth device types. The peer device must reside in a
+ *     different network namespace.
+ *
+ * Returns
+ *     The helper returns **TC_ACT_REDIRECT** on success or
+ *     **TC_ACT_SHOT** on error.
+ */
+static long (*bpf_redirect_peer)(__u32 ifindex, __u64 flags) = (void *) 155;
+
+/*
+ * bpf_task_storage_get
+ *
+ *     Get a bpf_local_storage from the *task*.
+ *
+ *     Logically, it could be thought of as getting the value from
+ *     a *map* with *task* as the **key**.  From this
+ *     perspective,  the usage is not much different from
+ *     **bpf_map_lookup_elem**\ (*map*, **&**\ *task*) except this
+ *     helper enforces the key must be an task_struct and the map must also
+ *     be a **BPF_MAP_TYPE_TASK_STORAGE**.
+ *
+ *     Underneath, the value is stored locally at *task* instead of
+ *     the *map*.  The *map* is used as the bpf-local-storage
+ *     "type". The bpf-local-storage "type" (i.e. the *map*) is
+ *     searched against all bpf_local_storage residing at *task*.
+ *
+ *     An optional *flags* (**BPF_LOCAL_STORAGE_GET_F_CREATE**) can be
+ *     used such that a new bpf_local_storage will be
+ *     created if one does not exist.  *value* can be used
+ *     together with **BPF_LOCAL_STORAGE_GET_F_CREATE** to specify
+ *     the initial value of a bpf_local_storage.  If *value* is
+ *     **NULL**, the new bpf_local_storage will be zero initialized.
+ *
+ * Returns
+ *     A bpf_local_storage pointer is returned on success.
+ *
+ *     **NULL** if not found or there was an error in adding
+ *     a new bpf_local_storage.
+ */
+static void *(*bpf_task_storage_get)(void *map, struct task_struct *task, void *value, __u64 flags) = (void *) 156;
+
+/*
+ * bpf_task_storage_delete
+ *
+ *     Delete a bpf_local_storage from a *task*.
+ *
+ * Returns
+ *     0 on success.
+ *
+ *     **-ENOENT** if the bpf_local_storage cannot be found.
+ */
+static long (*bpf_task_storage_delete)(void *map, struct task_struct *task) = (void *) 157;
+
+/*
+ * bpf_get_current_task_btf
+ *
+ *     Return a BTF pointer to the "current" task.
+ *     This pointer can also be used in helpers that accept an
+ *     *ARG_PTR_TO_BTF_ID* of type *task_struct*.
+ *
+ * Returns
+ *     Pointer to the current task.
+ */
+static struct task_struct *(*bpf_get_current_task_btf)(void) = (void *) 158;
+
+/*
+ * bpf_bprm_opts_set
+ *
+ *     Set or clear certain options on *bprm*:
+ *
+ *     **BPF_F_BPRM_SECUREEXEC** Set the secureexec bit
+ *     which sets the **AT_SECURE** auxv for glibc. The bit
+ *     is cleared if the flag is not specified.
+ *
+ * Returns
+ *     **-EINVAL** if invalid *flags* are passed, zero otherwise.
+ */
+static long (*bpf_bprm_opts_set)(struct linux_binprm *bprm, __u64 flags) = (void *) 159;
+
+/*
+ * bpf_ktime_get_coarse_ns
+ *
+ *     Return a coarse-grained version of the time elapsed since
+ *     system boot, in nanoseconds. Does not include time the system
+ *     was suspended.
+ *
+ *     See: **clock_gettime**\ (**CLOCK_MONOTONIC_COARSE**)
+ *
+ * Returns
+ *     Current *ktime*.
+ */
+static __u64 (*bpf_ktime_get_coarse_ns)(void) = (void *) 160;
+
+/*
+ * bpf_ima_inode_hash
+ *
+ *     Returns the stored IMA hash of the *inode* (if it's avaialable).
+ *     If the hash is larger than *size*, then only *size*
+ *     bytes will be copied to *dst*
+ *
+ * Returns
+ *     The **hash_algo** is returned on success,
+ *     **-EOPNOTSUP** if IMA is disabled or **-EINVAL** if
+ *     invalid arguments are passed.
+ */
+static long (*bpf_ima_inode_hash)(struct inode *inode, void *dst, __u32 size) = (void *) 161;
+
+/*
+ * bpf_sock_from_file
+ *
+ *     If the given file represents a socket, returns the associated
+ *     socket.
+ *
+ * Returns
+ *     A pointer to a struct socket on success or NULL if the file is
+ *     not a socket.
+ */
+static struct socket *(*bpf_sock_from_file)(struct file *file) = (void *) 162;
+
+/*
+ * bpf_check_mtu
+ *
+ *     Check packet size against exceeding MTU of net device (based
+ *     on *ifindex*).  This helper will likely be used in combination
+ *     with helpers that adjust/change the packet size.
+ *
+ *     The argument *len_diff* can be used for querying with a planned
+ *     size change. This allows to check MTU prior to changing packet
+ *     ctx. Providing an *len_diff* adjustment that is larger than the
+ *     actual packet size (resulting in negative packet size) will in
+ *     principle not exceed the MTU, why it is not considered a
+ *     failure.  Other BPF-helpers are needed for performing the
+ *     planned size change, why the responsability for catch a negative
+ *     packet size belong in those helpers.
+ *
+ *     Specifying *ifindex* zero means the MTU check is performed
+ *     against the current net device.  This is practical if this isn't
+ *     used prior to redirect.
+ *
+ *     On input *mtu_len* must be a valid pointer, else verifier will
+ *     reject BPF program.  If the value *mtu_len* is initialized to
+ *     zero then the ctx packet size is use.  When value *mtu_len* is
+ *     provided as input this specify the L3 length that the MTU check
+ *     is done against. Remember XDP and TC length operate at L2, but
+ *     this value is L3 as this correlate to MTU and IP-header tot_len
+ *     values which are L3 (similar behavior as bpf_fib_lookup).
+ *
+ *     The Linux kernel route table can configure MTUs on a more
+ *     specific per route level, which is not provided by this helper.
+ *     For route level MTU checks use the **bpf_fib_lookup**\ ()
+ *     helper.
+ *
+ *     *ctx* is either **struct xdp_md** for XDP programs or
+ *     **struct sk_buff** for tc cls_act programs.
+ *
+ *     The *flags* argument can be a combination of one or more of the
+ *     following values:
+ *
+ *     **BPF_MTU_CHK_SEGS**
+ *             This flag will only works for *ctx* **struct sk_buff**.
+ *             If packet context contains extra packet segment buffers
+ *             (often knows as GSO skb), then MTU check is harder to
+ *             check at this point, because in transmit path it is
+ *             possible for the skb packet to get re-segmented
+ *             (depending on net device features).  This could still be
+ *             a MTU violation, so this flag enables performing MTU
+ *             check against segments, with a different violation
+ *             return code to tell it apart. Check cannot use len_diff.
+ *
+ *     On return *mtu_len* pointer contains the MTU value of the net
+ *     device.  Remember the net device configured MTU is the L3 size,
+ *     which is returned here and XDP and TC length operate at L2.
+ *     Helper take this into account for you, but remember when using
+ *     MTU value in your BPF-code.
+ *
+ *
+ * Returns
+ *     * 0 on success, and populate MTU value in *mtu_len* pointer.
+ *
+ *     * < 0 if any input argument is invalid (*mtu_len* not updated)
+ *
+ *     MTU violations return positive values, but also populate MTU
+ *     value in *mtu_len* pointer, as this can be needed for
+ *     implementing PMTU handing:
+ *
+ *     * **BPF_MTU_CHK_RET_FRAG_NEEDED**
+ *     * **BPF_MTU_CHK_RET_SEGS_TOOBIG**
+ */
+static long (*bpf_check_mtu)(void *ctx, __u32 ifindex, __u32 *mtu_len, __s32 len_diff, __u64 flags) = (void *) 163;
+
+/*
+ * bpf_for_each_map_elem
+ *
+ *     For each element in **map**, call **callback_fn** function with
+ *     **map**, **callback_ctx** and other map-specific parameters.
+ *     The **callback_fn** should be a static function and
+ *     the **callback_ctx** should be a pointer to the stack.
+ *     The **flags** is used to control certain aspects of the helper.
+ *     Currently, the **flags** must be 0.
+ *
+ *     The following are a list of supported map types and their
+ *     respective expected callback signatures:
+ *
+ *     BPF_MAP_TYPE_HASH, BPF_MAP_TYPE_PERCPU_HASH,
+ *     BPF_MAP_TYPE_LRU_HASH, BPF_MAP_TYPE_LRU_PERCPU_HASH,
+ *     BPF_MAP_TYPE_ARRAY, BPF_MAP_TYPE_PERCPU_ARRAY
+ *
+ *     long (\*callback_fn)(struct bpf_map \*map, const void \*key, void \*value, void \*ctx);
+ *
+ *     For per_cpu maps, the map_value is the value on the cpu where the
+ *     bpf_prog is running.
+ *
+ *     If **callback_fn** return 0, the helper will continue to the next
+ *     element. If return value is 1, the helper will skip the rest of
+ *     elements and return. Other return values are not used now.
+ *
+ *
+ * Returns
+ *     The number of traversed map elements for success, **-EINVAL** for
+ *     invalid **flags**.
+ */
+static long (*bpf_for_each_map_elem)(void *map, void *callback_fn, void *callback_ctx, __u64 flags) = (void *) 164;
+
+/*
+ * bpf_snprintf
+ *
+ *     Outputs a string into the **str** buffer of size **str_size**
+ *     based on a format string stored in a read-only map pointed by
+ *     **fmt**.
+ *
+ *     Each format specifier in **fmt** corresponds to one u64 element
+ *     in the **data** array. For strings and pointers where pointees
+ *     are accessed, only the pointer values are stored in the *data*
+ *     array. The *data_len* is the size of *data* in bytes - must be
+ *     a multiple of 8.
+ *
+ *     Formats **%s** and **%p{i,I}{4,6}** require to read kernel
+ *     memory. Reading kernel memory may fail due to either invalid
+ *     address or valid address but requiring a major memory fault. If
+ *     reading kernel memory fails, the string for **%s** will be an
+ *     empty string, and the ip address for **%p{i,I}{4,6}** will be 0.
+ *     Not returning error to bpf program is consistent with what
+ *     **bpf_trace_printk**\ () does for now.
+ *
+ *
+ * Returns
+ *     The strictly positive length of the formatted string, including
+ *     the trailing zero character. If the return value is greater than
+ *     **str_size**, **str** contains a truncated string, guaranteed to
+ *     be zero-terminated except when **str_size** is 0.
+ *
+ *     Or **-EBUSY** if the per-CPU memory copy buffer is busy.
+ */
+static long (*bpf_snprintf)(char *str, __u32 str_size, const char *fmt, __u64 *data, __u32 data_len) = (void *) 165;
+
+/*
+ * bpf_sys_bpf
+ *
+ *     Execute bpf syscall with given arguments.
+ *
+ * Returns
+ *     A syscall result.
+ */
+static long (*bpf_sys_bpf)(__u32 cmd, void *attr, __u32 attr_size) = (void *) 166;
+
+/*
+ * bpf_btf_find_by_name_kind
+ *
+ *     Find BTF type with given name and kind in vmlinux BTF or in module's BTFs.
+ *
+ * Returns
+ *     Returns btf_id and btf_obj_fd in lower and upper 32 bits.
+ */
+static long (*bpf_btf_find_by_name_kind)(char *name, int name_sz, __u32 kind, int flags) = (void *) 167;
+
+/*
+ * bpf_sys_close
+ *
+ *     Execute close syscall for given FD.
+ *
+ * Returns
+ *     A syscall result.
+ */
+static long (*bpf_sys_close)(__u32 fd) = (void *) 168;
+
+/*
+ * bpf_timer_init
+ *
+ *     Initialize the timer.
+ *     First 4 bits of *flags* specify clockid.
+ *     Only CLOCK_MONOTONIC, CLOCK_REALTIME, CLOCK_BOOTTIME are allowed.
+ *     All other bits of *flags* are reserved.
+ *     The verifier will reject the program if *timer* is not from
+ *     the same *map*.
+ *
+ * Returns
+ *     0 on success.
+ *     **-EBUSY** if *timer* is already initialized.
+ *     **-EINVAL** if invalid *flags* are passed.
+ *     **-EPERM** if *timer* is in a map that doesn't have any user references.
+ *     The user space should either hold a file descriptor to a map with timers
+ *     or pin such map in bpffs. When map is unpinned or file descriptor is
+ *     closed all timers in the map will be cancelled and freed.
+ */
+static long (*bpf_timer_init)(struct bpf_timer *timer, void *map, __u64 flags) = (void *) 169;
+
+/*
+ * bpf_timer_set_callback
+ *
+ *     Configure the timer to call *callback_fn* static function.
+ *
+ * Returns
+ *     0 on success.
+ *     **-EINVAL** if *timer* was not initialized with bpf_timer_init() earlier.
+ *     **-EPERM** if *timer* is in a map that doesn't have any user references.
+ *     The user space should either hold a file descriptor to a map with timers
+ *     or pin such map in bpffs. When map is unpinned or file descriptor is
+ *     closed all timers in the map will be cancelled and freed.
+ */
+static long (*bpf_timer_set_callback)(struct bpf_timer *timer, void *callback_fn) = (void *) 170;
+
+/*
+ * bpf_timer_start
+ *
+ *     Set timer expiration N nanoseconds from the current time. The
+ *     configured callback will be invoked in soft irq context on some cpu
+ *     and will not repeat unless another bpf_timer_start() is made.
+ *     In such case the next invocation can migrate to a different cpu.
+ *     Since struct bpf_timer is a field inside map element the map
+ *     owns the timer. The bpf_timer_set_callback() will increment refcnt
+ *     of BPF program to make sure that callback_fn code stays valid.
+ *     When user space reference to a map reaches zero all timers
+ *     in a map are cancelled and corresponding program's refcnts are
+ *     decremented. This is done to make sure that Ctrl-C of a user
+ *     process doesn't leave any timers running. If map is pinned in
+ *     bpffs the callback_fn can re-arm itself indefinitely.
+ *     bpf_map_update/delete_elem() helpers and user space sys_bpf commands
+ *     cancel and free the timer in the given map element.
+ *     The map can contain timers that invoke callback_fn-s from different
+ *     programs. The same callback_fn can serve different timers from
+ *     different maps if key/value layout matches across maps.
+ *     Every bpf_timer_set_callback() can have different callback_fn.
+ *
+ *
+ * Returns
+ *     0 on success.
+ *     **-EINVAL** if *timer* was not initialized with bpf_timer_init() earlier
+ *     or invalid *flags* are passed.
+ */
+static long (*bpf_timer_start)(struct bpf_timer *timer, __u64 nsecs, __u64 flags) = (void *) 171;
+
+/*
+ * bpf_timer_cancel
+ *
+ *     Cancel the timer and wait for callback_fn to finish if it was running.
+ *
+ * Returns
+ *     0 if the timer was not active.
+ *     1 if the timer was active.
+ *     **-EINVAL** if *timer* was not initialized with bpf_timer_init() earlier.
+ *     **-EDEADLK** if callback_fn tried to call bpf_timer_cancel() on its
+ *     own timer which would have led to a deadlock otherwise.
+ */
+static long (*bpf_timer_cancel)(struct bpf_timer *timer) = (void *) 172;
+
+/*
+ * bpf_get_func_ip
+ *
+ *     Get address of the traced function (for tracing and kprobe programs).
+ *
+ * Returns
+ *     Address of the traced function.
+ */
+static __u64 (*bpf_get_func_ip)(void *ctx) = (void *) 173;
+
+/*
+ * bpf_get_attach_cookie
+ *
+ *     Get bpf_cookie value provided (optionally) during the program
+ *     attachment. It might be different for each individual
+ *     attachment, even if BPF program itself is the same.
+ *     Expects BPF program context *ctx* as a first argument.
+ *
+ *     Supported for the following program types:
+ *             - kprobe/uprobe;
+ *             - tracepoint;
+ *             - perf_event.
+ *
+ * Returns
+ *     Value specified by user at BPF link creation/attachment time
+ *     or 0, if it was not specified.
+ */
+static __u64 (*bpf_get_attach_cookie)(void *ctx) = (void *) 174;
+
+/*
+ * bpf_task_pt_regs
+ *
+ *     Get the struct pt_regs associated with **task**.
+ *
+ * Returns
+ *     A pointer to struct pt_regs.
+ */
+static long (*bpf_task_pt_regs)(struct task_struct *task) = (void *) 175;
+
+/*
+ * bpf_get_branch_snapshot
+ *
+ *     Get branch trace from hardware engines like Intel LBR. The
+ *     hardware engine is stopped shortly after the helper is
+ *     called. Therefore, the user need to filter branch entries
+ *     based on the actual use case. To capture branch trace
+ *     before the trigger point of the BPF program, the helper
+ *     should be called at the beginning of the BPF program.
+ *
+ *     The data is stored as struct perf_branch_entry into output
+ *     buffer *entries*. *size* is the size of *entries* in bytes.
+ *     *flags* is reserved for now and must be zero.
+ *
+ *
+ * Returns
+ *     On success, number of bytes written to *buf*. On error, a
+ *     negative value.
+ *
+ *     **-EINVAL** if *flags* is not zero.
+ *
+ *     **-ENOENT** if architecture does not support branch records.
+ */
+static long (*bpf_get_branch_snapshot)(void *entries, __u32 size, __u64 flags) = (void *) 176;
+
+/*
+ * bpf_trace_vprintk
+ *
+ *     Behaves like **bpf_trace_printk**\ () helper, but takes an array of u64
+ *     to format and can handle more format args as a result.
+ *
+ *     Arguments are to be used as in **bpf_seq_printf**\ () helper.
+ *
+ * Returns
+ *     The number of bytes written to the buffer, or a negative error
+ *     in case of failure.
+ */
+static long (*bpf_trace_vprintk)(const char *fmt, __u32 fmt_size, const void *data, __u32 data_len) = (void *) 177;
+
+/*
+ * bpf_skc_to_unix_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *unix_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct unix_sock *(*bpf_skc_to_unix_sock)(void *sk) = (void *) 178;
+
+/*
+ * bpf_kallsyms_lookup_name
+ *
+ *     Get the address of a kernel symbol, returned in *res*. *res* is
+ *     set to 0 if the symbol is not found.
+ *
+ * Returns
+ *     On success, zero. On error, a negative value.
+ *
+ *     **-EINVAL** if *flags* is not zero.
+ *
+ *     **-EINVAL** if string *name* is not the same size as *name_sz*.
+ *
+ *     **-ENOENT** if symbol is not found.
+ *
+ *     **-EPERM** if caller does not have permission to obtain kernel address.
+ */
+static long (*bpf_kallsyms_lookup_name)(const char *name, int name_sz, int flags, __u64 *res) = (void *) 179;
+
+/*
+ * bpf_find_vma
+ *
+ *     Find vma of *task* that contains *addr*, call *callback_fn*
+ *     function with *task*, *vma*, and *callback_ctx*.
+ *     The *callback_fn* should be a static function and
+ *     the *callback_ctx* should be a pointer to the stack.
+ *     The *flags* is used to control certain aspects of the helper.
+ *     Currently, the *flags* must be 0.
+ *
+ *     The expected callback signature is
+ *
+ *     long (\*callback_fn)(struct task_struct \*task, struct vm_area_struct \*vma, void \*callback_ctx);
+ *
+ *
+ * Returns
+ *     0 on success.
+ *     **-ENOENT** if *task->mm* is NULL, or no vma contains *addr*.
+ *     **-EBUSY** if failed to try lock mmap_lock.
+ *     **-EINVAL** for invalid **flags**.
+ */
+static long (*bpf_find_vma)(struct task_struct *task, __u64 addr, void *callback_fn, void *callback_ctx, __u64 flags) = (void *) 180;
+
+/*
+ * bpf_loop
+ *
+ *     For **nr_loops**, call **callback_fn** function
+ *     with **callback_ctx** as the context parameter.
+ *     The **callback_fn** should be a static function and
+ *     the **callback_ctx** should be a pointer to the stack.
+ *     The **flags** is used to control certain aspects of the helper.
+ *     Currently, the **flags** must be 0. Currently, nr_loops is
+ *     limited to 1 << 23 (~8 million) loops.
+ *
+ *     long (\*callback_fn)(u32 index, void \*ctx);
+ *
+ *     where **index** is the current index in the loop. The index
+ *     is zero-indexed.
+ *
+ *     If **callback_fn** returns 0, the helper will continue to the next
+ *     loop. If return value is 1, the helper will skip the rest of
+ *     the loops and return. Other return values are not used now,
+ *     and will be rejected by the verifier.
+ *
+ *
+ * Returns
+ *     The number of loops performed, **-EINVAL** for invalid **flags**,
+ *     **-E2BIG** if **nr_loops** exceeds the maximum number of loops.
+ */
+static long (*bpf_loop)(__u32 nr_loops, void *callback_fn, void *callback_ctx, __u64 flags) = (void *) 181;
+
+/*
+ * bpf_strncmp
+ *
+ *     Do strncmp() between **s1** and **s2**. **s1** doesn't need
+ *     to be null-terminated and **s1_sz** is the maximum storage
+ *     size of **s1**. **s2** must be a read-only string.
+ *
+ * Returns
+ *     An integer less than, equal to, or greater than zero
+ *     if the first **s1_sz** bytes of **s1** is found to be
+ *     less than, to match, or be greater than **s2**.
+ */
+static long (*bpf_strncmp)(const char *s1, __u32 s1_sz, const char *s2) = (void *) 182;
+
+/*
+ * bpf_get_func_arg
+ *
+ *     Get **n**-th argument (zero based) of the traced function (for tracing programs)
+ *     returned in **value**.
+ *
+ *
+ * Returns
+ *     0 on success.
+ *     **-EINVAL** if n >= arguments count of traced function.
+ */
+static long (*bpf_get_func_arg)(void *ctx, __u32 n, __u64 *value) = (void *) 183;
+
+/*
+ * bpf_get_func_ret
+ *
+ *     Get return value of the traced function (for tracing programs)
+ *     in **value**.
+ *
+ *
+ * Returns
+ *     0 on success.
+ *     **-EOPNOTSUPP** for tracing programs other than BPF_TRACE_FEXIT or BPF_MODIFY_RETURN.
+ */
+static long (*bpf_get_func_ret)(void *ctx, __u64 *value) = (void *) 184;
+
+/*
+ * bpf_get_func_arg_cnt
+ *
+ *     Get number of arguments of the traced function (for tracing programs).
+ *
+ *
+ * Returns
+ *     The number of arguments of the traced function.
+ */
+static long (*bpf_get_func_arg_cnt)(void *ctx) = (void *) 185;
+
+/*
+ * bpf_get_retval
+ *
+ *     Get the syscall's return value that will be returned to userspace.
+ *
+ *     This helper is currently supported by cgroup programs only.
+ *
+ * Returns
+ *     The syscall's return value.
+ */
+static int (*bpf_get_retval)(void) = (void *) 186;
+
+/*
+ * bpf_set_retval
+ *
+ *     Set the syscall's return value that will be returned to userspace.
+ *
+ *     This helper is currently supported by cgroup programs only.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static int (*bpf_set_retval)(int retval) = (void *) 187;
+
+/*
+ * bpf_xdp_get_buff_len
+ *
+ *     Get the total size of a given xdp buff (linear and paged area)
+ *
+ * Returns
+ *     The total size of a given xdp buffer.
+ */
+static __u64 (*bpf_xdp_get_buff_len)(struct xdp_md *xdp_md) = (void *) 188;
+
+/*
+ * bpf_xdp_load_bytes
+ *
+ *     This helper is provided as an easy way to load data from a
+ *     xdp buffer. It can be used to load *len* bytes from *offset* from
+ *     the frame associated to *xdp_md*, into the buffer pointed by
+ *     *buf*.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_xdp_load_bytes)(struct xdp_md *xdp_md, __u32 offset, void *buf, __u32 len) = (void *) 189;
+
+/*
+ * bpf_xdp_store_bytes
+ *
+ *     Store *len* bytes from buffer *buf* into the frame
+ *     associated to *xdp_md*, at *offset*.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_xdp_store_bytes)(struct xdp_md *xdp_md, __u32 offset, void *buf, __u32 len) = (void *) 190;
+
+/*
+ * bpf_copy_from_user_task
+ *
+ *     Read *size* bytes from user space address *user_ptr* in *tsk*'s
+ *     address space, and stores the data in *dst*. *flags* is not
+ *     used yet and is provided for future extensibility. This helper
+ *     can only be used by sleepable programs.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure. On error
+ *     *dst* buffer is zeroed out.
+ */
+static long (*bpf_copy_from_user_task)(void *dst, __u32 size, const void *user_ptr, struct task_struct *tsk, __u64 flags) = (void *) 191;
+
+/*
+ * bpf_skb_set_tstamp
+ *
+ *     Change the __sk_buff->tstamp_type to *tstamp_type*
+ *     and set *tstamp* to the __sk_buff->tstamp together.
+ *
+ *     If there is no need to change the __sk_buff->tstamp_type,
+ *     the tstamp value can be directly written to __sk_buff->tstamp
+ *     instead.
+ *
+ *     BPF_SKB_TSTAMP_DELIVERY_MONO is the only tstamp that
+ *     will be kept during bpf_redirect_*().  A non zero
+ *     *tstamp* must be used with the BPF_SKB_TSTAMP_DELIVERY_MONO
+ *     *tstamp_type*.
+ *
+ *     A BPF_SKB_TSTAMP_UNSPEC *tstamp_type* can only be used
+ *     with a zero *tstamp*.
+ *
+ *     Only IPv4 and IPv6 skb->protocol are supported.
+ *
+ *     This function is most useful when it needs to set a
+ *     mono delivery time to __sk_buff->tstamp and then
+ *     bpf_redirect_*() to the egress of an iface.  For example,
+ *     changing the (rcv) timestamp in __sk_buff->tstamp at
+ *     ingress to a mono delivery time and then bpf_redirect_*()
+ *     to sch_fq@phy-dev.
+ *
+ * Returns
+ *     0 on success.
+ *     **-EINVAL** for invalid input
+ *     **-EOPNOTSUPP** for unsupported protocol
+ */
+static long (*bpf_skb_set_tstamp)(struct __sk_buff *skb, __u64 tstamp, __u32 tstamp_type) = (void *) 192;
+
+/*
+ * bpf_ima_file_hash
+ *
+ *     Returns a calculated IMA hash of the *file*.
+ *     If the hash is larger than *size*, then only *size*
+ *     bytes will be copied to *dst*
+ *
+ * Returns
+ *     The **hash_algo** is returned on success,
+ *     **-EOPNOTSUP** if the hash calculation failed or **-EINVAL** if
+ *     invalid arguments are passed.
+ */
+static long (*bpf_ima_file_hash)(struct file *file, void *dst, __u32 size) = (void *) 193;
+
+/*
+ * bpf_kptr_xchg
+ *
+ *     Exchange kptr at pointer *map_value* with *ptr*, and return the
+ *     old value. *ptr* can be NULL, otherwise it must be a referenced
+ *     pointer which will be released when this helper is called.
+ *
+ * Returns
+ *     The old value of kptr (which can be NULL). The returned pointer
+ *     if not NULL, is a reference which must be released using its
+ *     corresponding release function, or moved into a BPF map before
+ *     program exit.
+ */
+static void *(*bpf_kptr_xchg)(void *map_value, void *ptr) = (void *) 194;
+
+/*
+ * bpf_map_lookup_percpu_elem
+ *
+ *     Perform a lookup in *percpu map* for an entry associated to
+ *     *key* on *cpu*.
+ *
+ * Returns
+ *     Map value associated to *key* on *cpu*, or **NULL** if no entry
+ *     was found or *cpu* is invalid.
+ */
+static void *(*bpf_map_lookup_percpu_elem)(void *map, const void *key, __u32 cpu) = (void *) 195;
+
+/*
+ * bpf_skc_to_mptcp_sock
+ *
+ *     Dynamically cast a *sk* pointer to a *mptcp_sock* pointer.
+ *
+ * Returns
+ *     *sk* if casting is valid, or **NULL** otherwise.
+ */
+static struct mptcp_sock *(*bpf_skc_to_mptcp_sock)(void *sk) = (void *) 196;
+
+/*
+ * bpf_dynptr_from_mem
+ *
+ *     Get a dynptr to local memory *data*.
+ *
+ *     *data* must be a ptr to a map value.
+ *     The maximum *size* supported is DYNPTR_MAX_SIZE.
+ *     *flags* is currently unused.
+ *
+ * Returns
+ *     0 on success, -E2BIG if the size exceeds DYNPTR_MAX_SIZE,
+ *     -EINVAL if flags is not 0.
+ */
+static long (*bpf_dynptr_from_mem)(void *data, __u32 size, __u64 flags, struct bpf_dynptr *ptr) = (void *) 197;
+
+/*
+ * bpf_ringbuf_reserve_dynptr
+ *
+ *     Reserve *size* bytes of payload in a ring buffer *ringbuf*
+ *     through the dynptr interface. *flags* must be 0.
+ *
+ *     Please note that a corresponding bpf_ringbuf_submit_dynptr or
+ *     bpf_ringbuf_discard_dynptr must be called on *ptr*, even if the
+ *     reservation fails. This is enforced by the verifier.
+ *
+ * Returns
+ *     0 on success, or a negative error in case of failure.
+ */
+static long (*bpf_ringbuf_reserve_dynptr)(void *ringbuf, __u32 size, __u64 flags, struct bpf_dynptr *ptr) = (void *) 198;
+
+/*
+ * bpf_ringbuf_submit_dynptr
+ *
+ *     Submit reserved ring buffer sample, pointed to by *data*,
+ *     through the dynptr interface. This is a no-op if the dynptr is
+ *     invalid/null.
+ *
+ *     For more information on *flags*, please see
+ *     'bpf_ringbuf_submit'.
+ *
+ * Returns
+ *     Nothing. Always succeeds.
+ */
+static void (*bpf_ringbuf_submit_dynptr)(struct bpf_dynptr *ptr, __u64 flags) = (void *) 199;
+
+/*
+ * bpf_ringbuf_discard_dynptr
+ *
+ *     Discard reserved ring buffer sample through the dynptr
+ *     interface. This is a no-op if the dynptr is invalid/null.
+ *
+ *     For more information on *flags*, please see
+ *     'bpf_ringbuf_discard'.
+ *
+ * Returns
+ *     Nothing. Always succeeds.
+ */
+static void (*bpf_ringbuf_discard_dynptr)(struct bpf_dynptr *ptr, __u64 flags) = (void *) 200;
+
+/*
+ * bpf_dynptr_read
+ *
+ *     Read *len* bytes from *src* into *dst*, starting from *offset*
+ *     into *src*.
+ *     *flags* is currently unused.
+ *
+ * Returns
+ *     0 on success, -E2BIG if *offset* + *len* exceeds the length
+ *     of *src*'s data, -EINVAL if *src* is an invalid dynptr or if
+ *     *flags* is not 0.
+ */
+static long (*bpf_dynptr_read)(void *dst, __u32 len, struct bpf_dynptr *src, __u32 offset, __u64 flags) = (void *) 201;
+
+/*
+ * bpf_dynptr_write
+ *
+ *     Write *len* bytes from *src* into *dst*, starting from *offset*
+ *     into *dst*.
+ *     *flags* is currently unused.
+ *
+ * Returns
+ *     0 on success, -E2BIG if *offset* + *len* exceeds the length
+ *     of *dst*'s data, -EINVAL if *dst* is an invalid dynptr or if *dst*
+ *     is a read-only dynptr or if *flags* is not 0.
+ */
+static long (*bpf_dynptr_write)(struct bpf_dynptr *dst, __u32 offset, void *src, __u32 len, __u64 flags) = (void *) 202;
+
+/*
+ * bpf_dynptr_data
+ *
+ *     Get a pointer to the underlying dynptr data.
+ *
+ *     *len* must be a statically known value. The returned data slice
+ *     is invalidated whenever the dynptr is invalidated.
+ *
+ * Returns
+ *     Pointer to the underlying dynptr data, NULL if the dynptr is
+ *     read-only, if the dynptr is invalid, or if the offset and length
+ *     is out of bounds.
+ */
+static void *(*bpf_dynptr_data)(struct bpf_dynptr *ptr, __u32 offset, __u32 len) = (void *) 203;
+
+/*
+ * bpf_tcp_raw_gen_syncookie_ipv4
+ *
+ *     Try to issue a SYN cookie for the packet with corresponding
+ *     IPv4/TCP headers, *iph* and *th*, without depending on a
+ *     listening socket.
+ *
+ *     *iph* points to the IPv4 header.
+ *
+ *     *th* points to the start of the TCP header, while *th_len*
+ *     contains the length of the TCP header (at least
+ *     **sizeof**\ (**struct tcphdr**)).
+ *
+ * Returns
+ *     On success, lower 32 bits hold the generated SYN cookie in
+ *     followed by 16 bits which hold the MSS value for that cookie,
+ *     and the top 16 bits are unused.
+ *
+ *     On failure, the returned value is one of the following:
+ *
+ *     **-EINVAL** if *th_len* is invalid.
+ */
+static __s64 (*bpf_tcp_raw_gen_syncookie_ipv4)(struct iphdr *iph, struct tcphdr *th, __u32 th_len) = (void *) 204;
+
+/*
+ * bpf_tcp_raw_gen_syncookie_ipv6
+ *
+ *     Try to issue a SYN cookie for the packet with corresponding
+ *     IPv6/TCP headers, *iph* and *th*, without depending on a
+ *     listening socket.
+ *
+ *     *iph* points to the IPv6 header.
+ *
+ *     *th* points to the start of the TCP header, while *th_len*
+ *     contains the length of the TCP header (at least
+ *     **sizeof**\ (**struct tcphdr**)).
+ *
+ * Returns
+ *     On success, lower 32 bits hold the generated SYN cookie in
+ *     followed by 16 bits which hold the MSS value for that cookie,
+ *     and the top 16 bits are unused.
+ *
+ *     On failure, the returned value is one of the following:
+ *
+ *     **-EINVAL** if *th_len* is invalid.
+ *
+ *     **-EPROTONOSUPPORT** if CONFIG_IPV6 is not builtin.
+ */
+static __s64 (*bpf_tcp_raw_gen_syncookie_ipv6)(struct ipv6hdr *iph, struct tcphdr *th, __u32 th_len) = (void *) 205;
+
+/*
+ * bpf_tcp_raw_check_syncookie_ipv4
+ *
+ *     Check whether *iph* and *th* contain a valid SYN cookie ACK
+ *     without depending on a listening socket.
+ *
+ *     *iph* points to the IPv4 header.
+ *
+ *     *th* points to the TCP header.
+ *
+ * Returns
+ *     0 if *iph* and *th* are a valid SYN cookie ACK.
+ *
+ *     On failure, the returned value is one of the following:
+ *
+ *     **-EACCES** if the SYN cookie is not valid.
+ */
+static long (*bpf_tcp_raw_check_syncookie_ipv4)(struct iphdr *iph, struct tcphdr *th) = (void *) 206;
+
+/*
+ * bpf_tcp_raw_check_syncookie_ipv6
+ *
+ *     Check whether *iph* and *th* contain a valid SYN cookie ACK
+ *     without depending on a listening socket.
+ *
+ *     *iph* points to the IPv6 header.
+ *
+ *     *th* points to the TCP header.
+ *
+ * Returns
+ *     0 if *iph* and *th* are a valid SYN cookie ACK.
+ *
+ *     On failure, the returned value is one of the following:
+ *
+ *     **-EACCES** if the SYN cookie is not valid.
+ *
+ *     **-EPROTONOSUPPORT** if CONFIG_IPV6 is not builtin.
+ */
+static long (*bpf_tcp_raw_check_syncookie_ipv6)(struct ipv6hdr *iph, struct tcphdr *th) = (void *) 207;
+
+
diff --git a/ebpf_prog/bpf_headers/bpf_helpers.h b/ebpf_prog/bpf_headers/bpf_helpers.h
new file mode 100644 (file)
index 0000000..d0855b2
--- /dev/null
@@ -0,0 +1,301 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+#ifndef __BPF_HELPERS__
+#define __BPF_HELPERS__
+
+/*
+ * Note that bpf programs need to include either
+ * vmlinux.h (auto-generated from BTF) or linux/types.h
+ * in advance since bpf_helper_defs.h uses such types
+ * as __u64.
+ */
+#include "bpf_helper_defs.h"
+
+#define __uint(name, val) int (*name)[val]
+#define __type(name, val) typeof(val) *name
+#define __array(name, val) typeof(val) *name[]
+
+/*
+ * Helper macro to place programs, maps, license in
+ * different sections in elf_bpf file. Section names
+ * are interpreted by libbpf depending on the context (BPF programs, BPF maps,
+ * extern variables, etc).
+ * To allow use of SEC() with externs (e.g., for extern .maps declarations),
+ * make sure __attribute__((unused)) doesn't trigger compilation warning.
+ */
+#if __GNUC__ && !__clang__
+
+/*
+ * Pragma macros are broken on GCC
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=55578
+ * https://gcc.gnu.org/bugzilla/show_bug.cgi?id=90400
+ */
+#define SEC(name) __attribute__((section(name), used))
+
+#else
+
+#define SEC(name) \
+       _Pragma("GCC diagnostic push")                                      \
+       _Pragma("GCC diagnostic ignored \"-Wignored-attributes\"")          \
+       __attribute__((section(name), used))                                \
+       _Pragma("GCC diagnostic pop")                                       \
+
+#endif
+
+/* Avoid 'linux/stddef.h' definition of '__always_inline'. */
+#undef __always_inline
+#define __always_inline inline __attribute__((always_inline))
+
+#ifndef __noinline
+#define __noinline __attribute__((noinline))
+#endif
+#ifndef __weak
+#define __weak __attribute__((weak))
+#endif
+
+/*
+ * Use __hidden attribute to mark a non-static BPF subprogram effectively
+ * static for BPF verifier's verification algorithm purposes, allowing more
+ * extensive and permissive BPF verification process, taking into account
+ * subprogram's caller context.
+ */
+#define __hidden __attribute__((visibility("hidden")))
+
+/* When utilizing vmlinux.h with BPF CO-RE, user BPF programs can't include
+ * any system-level headers (such as stddef.h, linux/version.h, etc), and
+ * commonly-used macros like NULL and KERNEL_VERSION aren't available through
+ * vmlinux.h. This just adds unnecessary hurdles and forces users to re-define
+ * them on their own. So as a convenience, provide such definitions here.
+ */
+#ifndef NULL
+#define NULL ((void *)0)
+#endif
+
+#ifndef KERNEL_VERSION
+#define KERNEL_VERSION(a, b, c) (((a) << 16) + ((b) << 8) + ((c) > 255 ? 255 : (c)))
+#endif
+
+/*
+ * Helper macros to manipulate data structures
+ */
+#ifndef offsetof
+#define offsetof(TYPE, MEMBER) ((unsigned long)&((TYPE *)0)->MEMBER)
+#endif
+#ifndef container_of
+#define container_of(ptr, type, member)                                \
+       ({                                                      \
+               void *__mptr = (void *)(ptr);                   \
+               ((type *)(__mptr - offsetof(type, member)));    \
+       })
+#endif
+
+/*
+ * Compiler (optimization) barrier.
+ */
+#ifndef barrier
+#define barrier() asm volatile("" ::: "memory")
+#endif
+
+/* Variable-specific compiler (optimization) barrier. It's a no-op which makes
+ * compiler believe that there is some black box modification of a given
+ * variable and thus prevents compiler from making extra assumption about its
+ * value and potential simplifications and optimizations on this variable.
+ *
+ * E.g., compiler might often delay or even omit 32-bit to 64-bit casting of
+ * a variable, making some code patterns unverifiable. Putting barrier_var()
+ * in place will ensure that cast is performed before the barrier_var()
+ * invocation, because compiler has to pessimistically assume that embedded
+ * asm section might perform some extra operations on that variable.
+ *
+ * This is a variable-specific variant of more global barrier().
+ */
+#ifndef barrier_var
+#define barrier_var(var) asm volatile("" : "=r"(var) : "0"(var))
+#endif
+
+/*
+ * Helper macro to throw a compilation error if __bpf_unreachable() gets
+ * built into the resulting code. This works given BPF back end does not
+ * implement __builtin_trap(). This is useful to assert that certain paths
+ * of the program code are never used and hence eliminated by the compiler.
+ *
+ * For example, consider a switch statement that covers known cases used by
+ * the program. __bpf_unreachable() can then reside in the default case. If
+ * the program gets extended such that a case is not covered in the switch
+ * statement, then it will throw a build error due to the default case not
+ * being compiled out.
+ */
+#ifndef __bpf_unreachable
+# define __bpf_unreachable()   __builtin_trap()
+#endif
+
+/*
+ * Helper function to perform a tail call with a constant/immediate map slot.
+ */
+#if __clang_major__ >= 8 && defined(__bpf__)
+static __always_inline void
+bpf_tail_call_static(void *ctx, const void *map, const __u32 slot)
+{
+       if (!__builtin_constant_p(slot))
+               __bpf_unreachable();
+
+       /*
+        * Provide a hard guarantee that LLVM won't optimize setting r2 (map
+        * pointer) and r3 (constant map index) from _different paths_ ending
+        * up at the _same_ call insn as otherwise we won't be able to use the
+        * jmpq/nopl retpoline-free patching by the x86-64 JIT in the kernel
+        * given they mismatch. See also d2e4c1e6c294 ("bpf: Constant map key
+        * tracking for prog array pokes") for details on verifier tracking.
+        *
+        * Note on clobber list: we need to stay in-line with BPF calling
+        * convention, so even if we don't end up using r0, r4, r5, we need
+        * to mark them as clobber so that LLVM doesn't end up using them
+        * before / after the call.
+        */
+       asm volatile("r1 = %[ctx]\n\t"
+                    "r2 = %[map]\n\t"
+                    "r3 = %[slot]\n\t"
+                    "call 12"
+                    :: [ctx]"r"(ctx), [map]"r"(map), [slot]"i"(slot)
+                    : "r0", "r1", "r2", "r3", "r4", "r5");
+}
+#endif
+
+/*
+ * Helper structure used by eBPF C program
+ * to describe BPF map attributes to libbpf loader
+ */
+struct bpf_map_defold {
+       unsigned int type;
+       unsigned int key_size;
+       unsigned int value_size;
+       unsigned int max_entries;
+       unsigned int map_flags;
+} __attribute__((deprecated("use BTF-defined maps in .maps section")));
+
+enum libbpf_pin_type {
+       LIBBPF_PIN_NONE,
+       /* PIN_BY_NAME: pin maps by name (in /sys/fs/bpf by default) */
+       LIBBPF_PIN_BY_NAME,
+};
+
+enum libbpf_tristate {
+       TRI_NO = 0,
+       TRI_YES = 1,
+       TRI_MODULE = 2,
+};
+
+#define __kconfig __attribute__((section(".kconfig")))
+#define __ksym __attribute__((section(".ksyms")))
+#define __kptr __attribute__((btf_type_tag("kptr")))
+#define __kptr_ref __attribute__((btf_type_tag("kptr_ref")))
+
+#ifndef ___bpf_concat
+#define ___bpf_concat(a, b) a ## b
+#endif
+#ifndef ___bpf_apply
+#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
+#endif
+#ifndef ___bpf_nth
+#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
+#endif
+#ifndef ___bpf_narg
+#define ___bpf_narg(...) \
+       ___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+#endif
+
+#define ___bpf_fill0(arr, p, x) do {} while (0)
+#define ___bpf_fill1(arr, p, x) arr[p] = x
+#define ___bpf_fill2(arr, p, x, args...) arr[p] = x; ___bpf_fill1(arr, p + 1, args)
+#define ___bpf_fill3(arr, p, x, args...) arr[p] = x; ___bpf_fill2(arr, p + 1, args)
+#define ___bpf_fill4(arr, p, x, args...) arr[p] = x; ___bpf_fill3(arr, p + 1, args)
+#define ___bpf_fill5(arr, p, x, args...) arr[p] = x; ___bpf_fill4(arr, p + 1, args)
+#define ___bpf_fill6(arr, p, x, args...) arr[p] = x; ___bpf_fill5(arr, p + 1, args)
+#define ___bpf_fill7(arr, p, x, args...) arr[p] = x; ___bpf_fill6(arr, p + 1, args)
+#define ___bpf_fill8(arr, p, x, args...) arr[p] = x; ___bpf_fill7(arr, p + 1, args)
+#define ___bpf_fill9(arr, p, x, args...) arr[p] = x; ___bpf_fill8(arr, p + 1, args)
+#define ___bpf_fill10(arr, p, x, args...) arr[p] = x; ___bpf_fill9(arr, p + 1, args)
+#define ___bpf_fill11(arr, p, x, args...) arr[p] = x; ___bpf_fill10(arr, p + 1, args)
+#define ___bpf_fill12(arr, p, x, args...) arr[p] = x; ___bpf_fill11(arr, p + 1, args)
+#define ___bpf_fill(arr, args...) \
+       ___bpf_apply(___bpf_fill, ___bpf_narg(args))(arr, 0, args)
+
+/*
+ * BPF_SEQ_PRINTF to wrap bpf_seq_printf to-be-printed values
+ * in a structure.
+ */
+#define BPF_SEQ_PRINTF(seq, fmt, args...)                      \
+({                                                             \
+       static const char ___fmt[] = fmt;                       \
+       unsigned long long ___param[___bpf_narg(args)];         \
+                                                               \
+       _Pragma("GCC diagnostic push")                          \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")  \
+       ___bpf_fill(___param, args);                            \
+       _Pragma("GCC diagnostic pop")                           \
+                                                               \
+       bpf_seq_printf(seq, ___fmt, sizeof(___fmt),             \
+                      ___param, sizeof(___param));             \
+})
+
+/*
+ * BPF_SNPRINTF wraps the bpf_snprintf helper with variadic arguments instead of
+ * an array of u64.
+ */
+#define BPF_SNPRINTF(out, out_size, fmt, args...)              \
+({                                                             \
+       static const char ___fmt[] = fmt;                       \
+       unsigned long long ___param[___bpf_narg(args)];         \
+                                                               \
+       _Pragma("GCC diagnostic push")                          \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")  \
+       ___bpf_fill(___param, args);                            \
+       _Pragma("GCC diagnostic pop")                           \
+                                                               \
+       bpf_snprintf(out, out_size, ___fmt,                     \
+                    ___param, sizeof(___param));               \
+})
+
+#ifdef BPF_NO_GLOBAL_DATA
+#define BPF_PRINTK_FMT_MOD
+#else
+#define BPF_PRINTK_FMT_MOD static const
+#endif
+
+#define __bpf_printk(fmt, ...)                         \
+({                                                     \
+       BPF_PRINTK_FMT_MOD char ____fmt[] = fmt;        \
+       bpf_trace_printk(____fmt, sizeof(____fmt),      \
+                        ##__VA_ARGS__);                \
+})
+
+/*
+ * __bpf_vprintk wraps the bpf_trace_vprintk helper with variadic arguments
+ * instead of an array of u64.
+ */
+#define __bpf_vprintk(fmt, args...)                            \
+({                                                             \
+       static const char ___fmt[] = fmt;                       \
+       unsigned long long ___param[___bpf_narg(args)];         \
+                                                               \
+       _Pragma("GCC diagnostic push")                          \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")  \
+       ___bpf_fill(___param, args);                            \
+       _Pragma("GCC diagnostic pop")                           \
+                                                               \
+       bpf_trace_vprintk(___fmt, sizeof(___fmt),               \
+                         ___param, sizeof(___param));          \
+})
+
+/* Use __bpf_printk when bpf_printk call has 3 or fewer fmt args
+ * Otherwise use __bpf_vprintk
+ */
+#define ___bpf_pick_printk(...) \
+       ___bpf_nth(_, ##__VA_ARGS__, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk,       \
+                  __bpf_vprintk, __bpf_vprintk, __bpf_vprintk, __bpf_vprintk,          \
+                  __bpf_vprintk, __bpf_vprintk, __bpf_printk /*3*/, __bpf_printk /*2*/,\
+                  __bpf_printk /*1*/, __bpf_printk /*0*/)
+
+/* Helper macro to print out debug messages */
+#define bpf_printk(fmt, args...) ___bpf_pick_printk(args)(fmt, ##args)
+
+#endif
diff --git a/ebpf_prog/bpf_headers/bpf_tracing.h b/ebpf_prog/bpf_headers/bpf_tracing.h
new file mode 100644 (file)
index 0000000..1bc0c95
--- /dev/null
@@ -0,0 +1,563 @@
+/* SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) */
+#ifndef __BPF_TRACING_H__
+#define __BPF_TRACING_H__
+
+#include "bpf_helpers.h"
+
+/* Scan the ARCH passed in from ARCH env variable (see Makefile) */
+#if defined(__TARGET_ARCH_x86)
+       #define bpf_target_x86
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_s390)
+       #define bpf_target_s390
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_arm)
+       #define bpf_target_arm
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_arm64)
+       #define bpf_target_arm64
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_mips)
+       #define bpf_target_mips
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_powerpc)
+       #define bpf_target_powerpc
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_sparc)
+       #define bpf_target_sparc
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_riscv)
+       #define bpf_target_riscv
+       #define bpf_target_defined
+#elif defined(__TARGET_ARCH_arc)
+       #define bpf_target_arc
+       #define bpf_target_defined
+#else
+
+/* Fall back to what the compiler says */
+#if defined(__x86_64__)
+       #define bpf_target_x86
+       #define bpf_target_defined
+#elif defined(__s390__)
+       #define bpf_target_s390
+       #define bpf_target_defined
+#elif defined(__arm__)
+       #define bpf_target_arm
+       #define bpf_target_defined
+#elif defined(__aarch64__)
+       #define bpf_target_arm64
+       #define bpf_target_defined
+#elif defined(__mips__)
+       #define bpf_target_mips
+       #define bpf_target_defined
+#elif defined(__powerpc__)
+       #define bpf_target_powerpc
+       #define bpf_target_defined
+#elif defined(__sparc__)
+       #define bpf_target_sparc
+       #define bpf_target_defined
+#elif defined(__riscv) && __riscv_xlen == 64
+       #define bpf_target_riscv
+       #define bpf_target_defined
+#elif defined(__arc__)
+       #define bpf_target_arc
+       #define bpf_target_defined
+#endif /* no compiler target */
+
+#endif
+
+#ifndef __BPF_TARGET_MISSING
+#define __BPF_TARGET_MISSING "GCC error \"Must specify a BPF target arch via __TARGET_ARCH_xxx\""
+#endif
+
+#if defined(bpf_target_x86)
+
+#if defined(__KERNEL__) || defined(__VMLINUX_H__)
+
+#define __PT_PARM1_REG di
+#define __PT_PARM2_REG si
+#define __PT_PARM3_REG dx
+#define __PT_PARM4_REG cx
+#define __PT_PARM5_REG r8
+#define __PT_RET_REG sp
+#define __PT_FP_REG bp
+#define __PT_RC_REG ax
+#define __PT_SP_REG sp
+#define __PT_IP_REG ip
+/* syscall uses r10 for PARM4 */
+#define PT_REGS_PARM4_SYSCALL(x) ((x)->r10)
+#define PT_REGS_PARM4_CORE_SYSCALL(x) BPF_CORE_READ(x, r10)
+
+#else
+
+#ifdef __i386__
+
+#define __PT_PARM1_REG eax
+#define __PT_PARM2_REG edx
+#define __PT_PARM3_REG ecx
+/* i386 kernel is built with -mregparm=3 */
+#define __PT_PARM4_REG __unsupported__
+#define __PT_PARM5_REG __unsupported__
+#define __PT_RET_REG esp
+#define __PT_FP_REG ebp
+#define __PT_RC_REG eax
+#define __PT_SP_REG esp
+#define __PT_IP_REG eip
+
+#else /* __i386__ */
+
+#define __PT_PARM1_REG rdi
+#define __PT_PARM2_REG rsi
+#define __PT_PARM3_REG rdx
+#define __PT_PARM4_REG rcx
+#define __PT_PARM5_REG r8
+#define __PT_RET_REG rsp
+#define __PT_FP_REG rbp
+#define __PT_RC_REG rax
+#define __PT_SP_REG rsp
+#define __PT_IP_REG rip
+/* syscall uses r10 for PARM4 */
+#define PT_REGS_PARM4_SYSCALL(x) ((x)->r10)
+#define PT_REGS_PARM4_CORE_SYSCALL(x) BPF_CORE_READ(x, r10)
+
+#endif /* __i386__ */
+
+#endif /* __KERNEL__ || __VMLINUX_H__ */
+
+#elif defined(bpf_target_s390)
+
+struct pt_regs___s390 {
+       unsigned long orig_gpr2;
+};
+
+/* s390 provides user_pt_regs instead of struct pt_regs to userspace */
+#define __PT_REGS_CAST(x) ((const user_pt_regs *)(x))
+#define __PT_PARM1_REG gprs[2]
+#define __PT_PARM2_REG gprs[3]
+#define __PT_PARM3_REG gprs[4]
+#define __PT_PARM4_REG gprs[5]
+#define __PT_PARM5_REG gprs[6]
+#define __PT_RET_REG grps[14]
+#define __PT_FP_REG gprs[11]   /* Works only with CONFIG_FRAME_POINTER */
+#define __PT_RC_REG gprs[2]
+#define __PT_SP_REG gprs[15]
+#define __PT_IP_REG psw.addr
+#define PT_REGS_PARM1_SYSCALL(x) PT_REGS_PARM1_CORE_SYSCALL(x)
+#define PT_REGS_PARM1_CORE_SYSCALL(x) BPF_CORE_READ((const struct pt_regs___s390 *)(x), orig_gpr2)
+
+#elif defined(bpf_target_arm)
+
+#define __PT_PARM1_REG uregs[0]
+#define __PT_PARM2_REG uregs[1]
+#define __PT_PARM3_REG uregs[2]
+#define __PT_PARM4_REG uregs[3]
+#define __PT_PARM5_REG uregs[4]
+#define __PT_RET_REG uregs[14]
+#define __PT_FP_REG uregs[11]  /* Works only with CONFIG_FRAME_POINTER */
+#define __PT_RC_REG uregs[0]
+#define __PT_SP_REG uregs[13]
+#define __PT_IP_REG uregs[12]
+
+#elif defined(bpf_target_arm64)
+
+struct pt_regs___arm64 {
+       unsigned long orig_x0;
+};
+
+/* arm64 provides struct user_pt_regs instead of struct pt_regs to userspace */
+#define __PT_REGS_CAST(x) ((const struct user_pt_regs *)(x))
+#define __PT_PARM1_REG regs[0]
+#define __PT_PARM2_REG regs[1]
+#define __PT_PARM3_REG regs[2]
+#define __PT_PARM4_REG regs[3]
+#define __PT_PARM5_REG regs[4]
+#define __PT_RET_REG regs[30]
+#define __PT_FP_REG regs[29]   /* Works only with CONFIG_FRAME_POINTER */
+#define __PT_RC_REG regs[0]
+#define __PT_SP_REG sp
+#define __PT_IP_REG pc
+#define PT_REGS_PARM1_SYSCALL(x) PT_REGS_PARM1_CORE_SYSCALL(x)
+#define PT_REGS_PARM1_CORE_SYSCALL(x) BPF_CORE_READ((const struct pt_regs___arm64 *)(x), orig_x0)
+
+#elif defined(bpf_target_mips)
+
+#define __PT_PARM1_REG regs[4]
+#define __PT_PARM2_REG regs[5]
+#define __PT_PARM3_REG regs[6]
+#define __PT_PARM4_REG regs[7]
+#define __PT_PARM5_REG regs[8]
+#define __PT_RET_REG regs[31]
+#define __PT_FP_REG regs[30]   /* Works only with CONFIG_FRAME_POINTER */
+#define __PT_RC_REG regs[2]
+#define __PT_SP_REG regs[29]
+#define __PT_IP_REG cp0_epc
+
+#elif defined(bpf_target_powerpc)
+
+#define __PT_PARM1_REG gpr[3]
+#define __PT_PARM2_REG gpr[4]
+#define __PT_PARM3_REG gpr[5]
+#define __PT_PARM4_REG gpr[6]
+#define __PT_PARM5_REG gpr[7]
+#define __PT_RET_REG regs[31]
+#define __PT_FP_REG __unsupported__
+#define __PT_RC_REG gpr[3]
+#define __PT_SP_REG sp
+#define __PT_IP_REG nip
+/* powerpc does not select ARCH_HAS_SYSCALL_WRAPPER. */
+#define PT_REGS_SYSCALL_REGS(ctx) ctx
+
+#elif defined(bpf_target_sparc)
+
+#define __PT_PARM1_REG u_regs[UREG_I0]
+#define __PT_PARM2_REG u_regs[UREG_I1]
+#define __PT_PARM3_REG u_regs[UREG_I2]
+#define __PT_PARM4_REG u_regs[UREG_I3]
+#define __PT_PARM5_REG u_regs[UREG_I4]
+#define __PT_RET_REG u_regs[UREG_I7]
+#define __PT_FP_REG __unsupported__
+#define __PT_RC_REG u_regs[UREG_I0]
+#define __PT_SP_REG u_regs[UREG_FP]
+/* Should this also be a bpf_target check for the sparc case? */
+#if defined(__arch64__)
+#define __PT_IP_REG tpc
+#else
+#define __PT_IP_REG pc
+#endif
+
+#elif defined(bpf_target_riscv)
+
+#define __PT_REGS_CAST(x) ((const struct user_regs_struct *)(x))
+#define __PT_PARM1_REG a0
+#define __PT_PARM2_REG a1
+#define __PT_PARM3_REG a2
+#define __PT_PARM4_REG a3
+#define __PT_PARM5_REG a4
+#define __PT_RET_REG ra
+#define __PT_FP_REG s0
+#define __PT_RC_REG a0
+#define __PT_SP_REG sp
+#define __PT_IP_REG pc
+/* riscv does not select ARCH_HAS_SYSCALL_WRAPPER. */
+#define PT_REGS_SYSCALL_REGS(ctx) ctx
+
+#elif defined(bpf_target_arc)
+
+/* arc provides struct user_pt_regs instead of struct pt_regs to userspace */
+#define __PT_REGS_CAST(x) ((const struct user_regs_struct *)(x))
+#define __PT_PARM1_REG scratch.r0
+#define __PT_PARM2_REG scratch.r1
+#define __PT_PARM3_REG scratch.r2
+#define __PT_PARM4_REG scratch.r3
+#define __PT_PARM5_REG scratch.r4
+#define __PT_RET_REG scratch.blink
+#define __PT_FP_REG __unsupported__
+#define __PT_RC_REG scratch.r0
+#define __PT_SP_REG scratch.sp
+#define __PT_IP_REG scratch.ret
+/* arc does not select ARCH_HAS_SYSCALL_WRAPPER. */
+#define PT_REGS_SYSCALL_REGS(ctx) ctx
+
+#endif
+
+#if defined(bpf_target_defined)
+
+struct pt_regs;
+
+/* allow some architecutres to override `struct pt_regs` */
+#ifndef __PT_REGS_CAST
+#define __PT_REGS_CAST(x) (x)
+#endif
+
+#define PT_REGS_PARM1(x) (__PT_REGS_CAST(x)->__PT_PARM1_REG)
+#define PT_REGS_PARM2(x) (__PT_REGS_CAST(x)->__PT_PARM2_REG)
+#define PT_REGS_PARM3(x) (__PT_REGS_CAST(x)->__PT_PARM3_REG)
+#define PT_REGS_PARM4(x) (__PT_REGS_CAST(x)->__PT_PARM4_REG)
+#define PT_REGS_PARM5(x) (__PT_REGS_CAST(x)->__PT_PARM5_REG)
+#define PT_REGS_RET(x) (__PT_REGS_CAST(x)->__PT_RET_REG)
+#define PT_REGS_FP(x) (__PT_REGS_CAST(x)->__PT_FP_REG)
+#define PT_REGS_RC(x) (__PT_REGS_CAST(x)->__PT_RC_REG)
+#define PT_REGS_SP(x) (__PT_REGS_CAST(x)->__PT_SP_REG)
+#define PT_REGS_IP(x) (__PT_REGS_CAST(x)->__PT_IP_REG)
+
+#define PT_REGS_PARM1_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM1_REG)
+#define PT_REGS_PARM2_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM2_REG)
+#define PT_REGS_PARM3_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM3_REG)
+#define PT_REGS_PARM4_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM4_REG)
+#define PT_REGS_PARM5_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_PARM5_REG)
+#define PT_REGS_RET_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_RET_REG)
+#define PT_REGS_FP_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_FP_REG)
+#define PT_REGS_RC_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_RC_REG)
+#define PT_REGS_SP_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_SP_REG)
+#define PT_REGS_IP_CORE(x) BPF_CORE_READ(__PT_REGS_CAST(x), __PT_IP_REG)
+
+#if defined(bpf_target_powerpc)
+
+#define BPF_KPROBE_READ_RET_IP(ip, ctx)                ({ (ip) = (ctx)->link; })
+#define BPF_KRETPROBE_READ_RET_IP              BPF_KPROBE_READ_RET_IP
+
+#elif defined(bpf_target_sparc)
+
+#define BPF_KPROBE_READ_RET_IP(ip, ctx)                ({ (ip) = PT_REGS_RET(ctx); })
+#define BPF_KRETPROBE_READ_RET_IP              BPF_KPROBE_READ_RET_IP
+
+#else
+
+#define BPF_KPROBE_READ_RET_IP(ip, ctx)                                            \
+       ({ bpf_probe_read_kernel(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); })
+#define BPF_KRETPROBE_READ_RET_IP(ip, ctx)                                 \
+       ({ bpf_probe_read_kernel(&(ip), sizeof(ip), (void *)(PT_REGS_FP(ctx) + sizeof(ip))); })
+
+#endif
+
+#ifndef PT_REGS_PARM1_SYSCALL
+#define PT_REGS_PARM1_SYSCALL(x) PT_REGS_PARM1(x)
+#endif
+#define PT_REGS_PARM2_SYSCALL(x) PT_REGS_PARM2(x)
+#define PT_REGS_PARM3_SYSCALL(x) PT_REGS_PARM3(x)
+#ifndef PT_REGS_PARM4_SYSCALL
+#define PT_REGS_PARM4_SYSCALL(x) PT_REGS_PARM4(x)
+#endif
+#define PT_REGS_PARM5_SYSCALL(x) PT_REGS_PARM5(x)
+
+#ifndef PT_REGS_PARM1_CORE_SYSCALL
+#define PT_REGS_PARM1_CORE_SYSCALL(x) PT_REGS_PARM1_CORE(x)
+#endif
+#define PT_REGS_PARM2_CORE_SYSCALL(x) PT_REGS_PARM2_CORE(x)
+#define PT_REGS_PARM3_CORE_SYSCALL(x) PT_REGS_PARM3_CORE(x)
+#ifndef PT_REGS_PARM4_CORE_SYSCALL
+#define PT_REGS_PARM4_CORE_SYSCALL(x) PT_REGS_PARM4_CORE(x)
+#endif
+#define PT_REGS_PARM5_CORE_SYSCALL(x) PT_REGS_PARM5_CORE(x)
+
+#else /* defined(bpf_target_defined) */
+
+#define PT_REGS_PARM1(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM2(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM3(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM4(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM5(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_RET(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_FP(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_RC(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_SP(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_IP(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+
+#define PT_REGS_PARM1_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM2_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM3_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM4_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM5_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_RET_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_FP_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_RC_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_SP_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_IP_CORE(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+
+#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+
+#define PT_REGS_PARM1_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM2_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM3_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM4_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM5_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+
+#define PT_REGS_PARM1_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM2_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM3_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM4_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+#define PT_REGS_PARM5_CORE_SYSCALL(x) ({ _Pragma(__BPF_TARGET_MISSING); 0l; })
+
+#endif /* defined(bpf_target_defined) */
+
+/*
+ * When invoked from a syscall handler kprobe, returns a pointer to a
+ * struct pt_regs containing syscall arguments and suitable for passing to
+ * PT_REGS_PARMn_SYSCALL() and PT_REGS_PARMn_CORE_SYSCALL().
+ */
+#ifndef PT_REGS_SYSCALL_REGS
+/* By default, assume that the arch selects ARCH_HAS_SYSCALL_WRAPPER. */
+#define PT_REGS_SYSCALL_REGS(ctx) ((struct pt_regs *)PT_REGS_PARM1(ctx))
+#endif
+
+#ifndef ___bpf_concat
+#define ___bpf_concat(a, b) a ## b
+#endif
+#ifndef ___bpf_apply
+#define ___bpf_apply(fn, n) ___bpf_concat(fn, n)
+#endif
+#ifndef ___bpf_nth
+#define ___bpf_nth(_, _1, _2, _3, _4, _5, _6, _7, _8, _9, _a, _b, _c, N, ...) N
+#endif
+#ifndef ___bpf_narg
+#define ___bpf_narg(...) ___bpf_nth(_, ##__VA_ARGS__, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
+#endif
+
+#define ___bpf_ctx_cast0()            ctx
+#define ___bpf_ctx_cast1(x)           ___bpf_ctx_cast0(), (void *)ctx[0]
+#define ___bpf_ctx_cast2(x, args...)  ___bpf_ctx_cast1(args), (void *)ctx[1]
+#define ___bpf_ctx_cast3(x, args...)  ___bpf_ctx_cast2(args), (void *)ctx[2]
+#define ___bpf_ctx_cast4(x, args...)  ___bpf_ctx_cast3(args), (void *)ctx[3]
+#define ___bpf_ctx_cast5(x, args...)  ___bpf_ctx_cast4(args), (void *)ctx[4]
+#define ___bpf_ctx_cast6(x, args...)  ___bpf_ctx_cast5(args), (void *)ctx[5]
+#define ___bpf_ctx_cast7(x, args...)  ___bpf_ctx_cast6(args), (void *)ctx[6]
+#define ___bpf_ctx_cast8(x, args...)  ___bpf_ctx_cast7(args), (void *)ctx[7]
+#define ___bpf_ctx_cast9(x, args...)  ___bpf_ctx_cast8(args), (void *)ctx[8]
+#define ___bpf_ctx_cast10(x, args...) ___bpf_ctx_cast9(args), (void *)ctx[9]
+#define ___bpf_ctx_cast11(x, args...) ___bpf_ctx_cast10(args), (void *)ctx[10]
+#define ___bpf_ctx_cast12(x, args...) ___bpf_ctx_cast11(args), (void *)ctx[11]
+#define ___bpf_ctx_cast(args...)      ___bpf_apply(___bpf_ctx_cast, ___bpf_narg(args))(args)
+
+/*
+ * BPF_PROG is a convenience wrapper for generic tp_btf/fentry/fexit and
+ * similar kinds of BPF programs, that accept input arguments as a single
+ * pointer to untyped u64 array, where each u64 can actually be a typed
+ * pointer or integer of different size. Instead of requring user to write
+ * manual casts and work with array elements by index, BPF_PROG macro
+ * allows user to declare a list of named and typed input arguments in the
+ * same syntax as for normal C function. All the casting is hidden and
+ * performed transparently, while user code can just assume working with
+ * function arguments of specified type and name.
+ *
+ * Original raw context argument is preserved as well as 'ctx' argument.
+ * This is useful when using BPF helpers that expect original context
+ * as one of the parameters (e.g., for bpf_perf_event_output()).
+ */
+#define BPF_PROG(name, args...)                                                    \
+name(unsigned long long *ctx);                                             \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(unsigned long long *ctx, ##args);                               \
+typeof(name(0)) name(unsigned long long *ctx)                              \
+{                                                                          \
+       _Pragma("GCC diagnostic push")                                      \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")              \
+       return ____##name(___bpf_ctx_cast(args));                           \
+       _Pragma("GCC diagnostic pop")                                       \
+}                                                                          \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(unsigned long long *ctx, ##args)
+
+struct pt_regs;
+
+#define ___bpf_kprobe_args0()           ctx
+#define ___bpf_kprobe_args1(x)          ___bpf_kprobe_args0(), (void *)PT_REGS_PARM1(ctx)
+#define ___bpf_kprobe_args2(x, args...) ___bpf_kprobe_args1(args), (void *)PT_REGS_PARM2(ctx)
+#define ___bpf_kprobe_args3(x, args...) ___bpf_kprobe_args2(args), (void *)PT_REGS_PARM3(ctx)
+#define ___bpf_kprobe_args4(x, args...) ___bpf_kprobe_args3(args), (void *)PT_REGS_PARM4(ctx)
+#define ___bpf_kprobe_args5(x, args...) ___bpf_kprobe_args4(args), (void *)PT_REGS_PARM5(ctx)
+#define ___bpf_kprobe_args(args...)     ___bpf_apply(___bpf_kprobe_args, ___bpf_narg(args))(args)
+
+/*
+ * BPF_KPROBE serves the same purpose for kprobes as BPF_PROG for
+ * tp_btf/fentry/fexit BPF programs. It hides the underlying platform-specific
+ * low-level way of getting kprobe input arguments from struct pt_regs, and
+ * provides a familiar typed and named function arguments syntax and
+ * semantics of accessing kprobe input paremeters.
+ *
+ * Original struct pt_regs* context is preserved as 'ctx' argument. This might
+ * be necessary when using BPF helpers like bpf_perf_event_output().
+ */
+#define BPF_KPROBE(name, args...)                                          \
+name(struct pt_regs *ctx);                                                 \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(struct pt_regs *ctx, ##args);                                   \
+typeof(name(0)) name(struct pt_regs *ctx)                                  \
+{                                                                          \
+       _Pragma("GCC diagnostic push")                                      \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")              \
+       return ____##name(___bpf_kprobe_args(args));                        \
+       _Pragma("GCC diagnostic pop")                                       \
+}                                                                          \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(struct pt_regs *ctx, ##args)
+
+#define ___bpf_kretprobe_args0()       ctx
+#define ___bpf_kretprobe_args1(x)      ___bpf_kretprobe_args0(), (void *)PT_REGS_RC(ctx)
+#define ___bpf_kretprobe_args(args...) ___bpf_apply(___bpf_kretprobe_args, ___bpf_narg(args))(args)
+
+/*
+ * BPF_KRETPROBE is similar to BPF_KPROBE, except, it only provides optional
+ * return value (in addition to `struct pt_regs *ctx`), but no input
+ * arguments, because they will be clobbered by the time probed function
+ * returns.
+ */
+#define BPF_KRETPROBE(name, args...)                                       \
+name(struct pt_regs *ctx);                                                 \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(struct pt_regs *ctx, ##args);                                   \
+typeof(name(0)) name(struct pt_regs *ctx)                                  \
+{                                                                          \
+       _Pragma("GCC diagnostic push")                                      \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")              \
+       return ____##name(___bpf_kretprobe_args(args));                     \
+       _Pragma("GCC diagnostic pop")                                       \
+}                                                                          \
+static __always_inline typeof(name(0)) ____##name(struct pt_regs *ctx, ##args)
+
+/* If kernel has CONFIG_ARCH_HAS_SYSCALL_WRAPPER, read pt_regs directly */
+#define ___bpf_syscall_args0()           ctx
+#define ___bpf_syscall_args1(x)          ___bpf_syscall_args0(), (void *)PT_REGS_PARM1_SYSCALL(regs)
+#define ___bpf_syscall_args2(x, args...) ___bpf_syscall_args1(args), (void *)PT_REGS_PARM2_SYSCALL(regs)
+#define ___bpf_syscall_args3(x, args...) ___bpf_syscall_args2(args), (void *)PT_REGS_PARM3_SYSCALL(regs)
+#define ___bpf_syscall_args4(x, args...) ___bpf_syscall_args3(args), (void *)PT_REGS_PARM4_SYSCALL(regs)
+#define ___bpf_syscall_args5(x, args...) ___bpf_syscall_args4(args), (void *)PT_REGS_PARM5_SYSCALL(regs)
+#define ___bpf_syscall_args(args...)     ___bpf_apply(___bpf_syscall_args, ___bpf_narg(args))(args)
+
+/* If kernel doesn't have CONFIG_ARCH_HAS_SYSCALL_WRAPPER, we have to BPF_CORE_READ from pt_regs */
+#define ___bpf_syswrap_args0()           ctx
+#define ___bpf_syswrap_args1(x)          ___bpf_syswrap_args0(), (void *)PT_REGS_PARM1_CORE_SYSCALL(regs)
+#define ___bpf_syswrap_args2(x, args...) ___bpf_syswrap_args1(args), (void *)PT_REGS_PARM2_CORE_SYSCALL(regs)
+#define ___bpf_syswrap_args3(x, args...) ___bpf_syswrap_args2(args), (void *)PT_REGS_PARM3_CORE_SYSCALL(regs)
+#define ___bpf_syswrap_args4(x, args...) ___bpf_syswrap_args3(args), (void *)PT_REGS_PARM4_CORE_SYSCALL(regs)
+#define ___bpf_syswrap_args5(x, args...) ___bpf_syswrap_args4(args), (void *)PT_REGS_PARM5_CORE_SYSCALL(regs)
+#define ___bpf_syswrap_args(args...)     ___bpf_apply(___bpf_syswrap_args, ___bpf_narg(args))(args)
+
+/*
+ * BPF_KSYSCALL is a variant of BPF_KPROBE, which is intended for
+ * tracing syscall functions, like __x64_sys_close. It hides the underlying
+ * platform-specific low-level way of getting syscall input arguments from
+ * struct pt_regs, and provides a familiar typed and named function arguments
+ * syntax and semantics of accessing syscall input parameters.
+ *
+ * Original struct pt_regs * context is preserved as 'ctx' argument. This might
+ * be necessary when using BPF helpers like bpf_perf_event_output().
+ *
+ * At the moment BPF_KSYSCALL does not transparently handle all the calling
+ * convention quirks for the following syscalls:
+ *
+ * - mmap(): __ARCH_WANT_SYS_OLD_MMAP.
+ * - clone(): CONFIG_CLONE_BACKWARDS, CONFIG_CLONE_BACKWARDS2 and
+ *            CONFIG_CLONE_BACKWARDS3.
+ * - socket-related syscalls: __ARCH_WANT_SYS_SOCKETCALL.
+ * - compat syscalls.
+ *
+ * This may or may not change in the future. User needs to take extra measures
+ * to handle such quirks explicitly, if necessary.
+ *
+ * This macro relies on BPF CO-RE support and virtual __kconfig externs.
+ */
+#define BPF_KSYSCALL(name, args...)                                        \
+name(struct pt_regs *ctx);                                                 \
+extern _Bool LINUX_HAS_SYSCALL_WRAPPER __kconfig;                          \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(struct pt_regs *ctx, ##args);                                   \
+typeof(name(0)) name(struct pt_regs *ctx)                                  \
+{                                                                          \
+       struct pt_regs *regs = LINUX_HAS_SYSCALL_WRAPPER                    \
+                              ? (struct pt_regs *)PT_REGS_PARM1(ctx)       \
+                              : ctx;                                       \
+       _Pragma("GCC diagnostic push")                                      \
+       _Pragma("GCC diagnostic ignored \"-Wint-conversion\"")              \
+       if (LINUX_HAS_SYSCALL_WRAPPER)                                      \
+               return ____##name(___bpf_syswrap_args(args));               \
+       else                                                                \
+               return ____##name(___bpf_syscall_args(args));               \
+       _Pragma("GCC diagnostic pop")                                       \
+}                                                                          \
+static __attribute__((always_inline)) typeof(name(0))                      \
+____##name(struct pt_regs *ctx, ##args)
+
+#define BPF_KPROBE_SYSCALL BPF_KSYSCALL
+
+#endif
diff --git a/ebpf_prog/common.h b/ebpf_prog/common.h
new file mode 100644 (file)
index 0000000..70d3dea
--- /dev/null
@@ -0,0 +1,97 @@
+#ifndef OPENSNITCH_COMMON_H
+#define OPENSNITCH_COMMON_H
+
+#include "common_defs.h"
+
+//https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/limits.h#L13
+#ifndef MAX_PATH_LEN
+ #define MAX_PATH_LEN  4096
+#endif
+
+//https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/binfmts.h#L16
+#define MAX_CMDLINE_LEN 4096
+// max args that I've been able to use before hitting the error:
+// "dereference of modified ctx ptr disallowed"
+#define MAX_ARGS 20
+#define MAX_ARG_SIZE 256
+
+// flags to indicate if we were able to read all the cmdline arguments,
+// or if one of the arguments is >= MAX_ARG_SIZE, or there more than MAX_ARGS
+#define COMPLETE_ARGS 0
+#define INCOMPLETE_ARGS 1
+
+#ifndef TASK_COMM_LEN
+ #define TASK_COMM_LEN 16
+#endif
+
+#define BUF_SIZE_MAP_NS 256
+#define GLOBAL_MAP_NS "256"
+enum events_type {
+    EVENT_NONE = 0,
+    EVENT_EXEC,
+    EVENT_EXECVEAT,
+    EVENT_FORK,
+    EVENT_SCHED_EXIT,
+};
+
+struct trace_ev_common {
+    short common_type;
+    char common_flags;
+    char common_preempt_count;
+    int common_pid;
+};
+
+struct trace_sys_enter_execve {
+    struct trace_ev_common ext;
+
+    int __syscall_nr;
+    char *filename;
+    const char *const *argv;
+    const char *const *envp;
+};
+
+struct trace_sys_enter_execveat {
+    struct trace_ev_common ext;
+
+    int __syscall_nr;
+    char *filename;
+    const char *const *argv;
+    const char *const *envp;
+    int flags;
+};
+
+struct trace_sys_exit_execve {
+    struct trace_ev_common ext;
+
+    int __syscall_nr;
+    long ret;
+};
+
+
+struct data_t {
+    u64 type;
+    u32 pid;  // PID as in the userspace term (i.e. task->tgid in kernel)
+    u32 uid;
+    // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel)
+    u32 ppid;
+    u32 ret_code;
+    u8 args_count;
+    u8 args_partial;
+    char filename[MAX_PATH_LEN];
+    char args[MAX_ARGS][MAX_ARG_SIZE];
+    char comm[TASK_COMM_LEN];
+    u16 pad1;
+    u32 pad2;
+};
+
+//-----------------------------------------------------------------------------
+// maps
+
+struct bpf_map_def SEC("maps/heapstore") heapstore = {
+       .type = BPF_MAP_TYPE_PERCPU_ARRAY,
+       .key_size = sizeof(u32),
+       .value_size = sizeof(struct data_t),
+       .max_entries = 1
+};
+
+#endif
diff --git a/ebpf_prog/common_defs.h b/ebpf_prog/common_defs.h
new file mode 100644 (file)
index 0000000..da3c0d1
--- /dev/null
@@ -0,0 +1,40 @@
+#ifndef OPENSNITCH_COMMON_DEFS_H
+#define OPENSNITCH_COMMON_DEFS_H
+
+#include <linux/sched.h>
+#include <linux/ptrace.h>
+#include <uapi/linux/bpf.h>
+#include "bpf_headers/bpf_helpers.h"
+#include "bpf_headers/bpf_tracing.h"
+//#include <bpf/bpf_core_read.h> 
+
+#define BUF_SIZE_MAP_NS 256
+#define MAPSIZE 12000
+
+// even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32
+typedef u64 pid_size_t;
+typedef u64 uid_size_t; 
+
+
+//-------------------------------map definitions 
+// which github.com/iovisor/gobpf/elf expects
+typedef struct bpf_map_def {
+       unsigned int type;
+       unsigned int key_size;
+       unsigned int value_size;
+       unsigned int max_entries;
+       unsigned int map_flags;
+       unsigned int pinning;
+       char namespace[BUF_SIZE_MAP_NS];
+} bpf_map_def;
+
+enum bpf_pin_type {
+       PIN_NONE = 0,
+       PIN_OBJECT_NS,
+       PIN_GLOBAL_NS,
+       PIN_CUSTOM_NS,
+};
+//-----------------------------------
+
+#endif
+
diff --git a/ebpf_prog/opensnitch-dns.c b/ebpf_prog/opensnitch-dns.c
new file mode 100644 (file)
index 0000000..7bb01b7
--- /dev/null
@@ -0,0 +1,235 @@
+/*   Copyright (C) 2022      calesanz
+//                 2023-2024 Gustavo Iñiguez Goya
+//
+//   This file is part of OpenSnitch.
+//
+//   OpenSnitch is free software: you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation, either version 3 of the License, or
+//   (at your option) any later version.
+//
+//   OpenSnitch is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with OpenSnitch.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#define KBUILD_MODNAME "opensnitch-dns"
+
+#include <linux/in.h>
+#include <linux/in6.h>
+#include <linux/ptrace.h>
+#include <linux/sched.h>
+#include <net/sock.h>
+#include <uapi/linux/bpf.h>
+#include <uapi/linux/tcp.h>
+#include "common_defs.h"
+#include "bpf_headers/bpf_helpers.h"
+#include "bpf_headers/bpf_tracing.h"
+
+//-----------------------------------
+
+// random values
+#define MAX_ALIASES 5
+#define MAX_IPS 30
+
+struct nameLookupEvent {
+    u32 addr_type;
+    u8 ip[16];
+    char host[252];
+} __attribute__((packed));
+
+struct hostent {
+    char *h_name;       /* Official name of host.  */
+    char **h_aliases;   /* Alias list.  */
+    int h_addrtype;     /* Host address type.  */
+    int h_length;       /* Length of address.  */
+    char **h_addr_list; /* List of addresses from name server.  */
+#ifdef __USE_MISC
+#define h_addr h_addr_list[0] /* Address, for backward compatibility.*/
+#endif
+};
+
+struct addrinfo {
+    int ai_flags;             /* Input flags.  */
+    int ai_family;            /* Protocol family for socket.  */
+    int ai_socktype;          /* Socket type.  */
+    int ai_protocol;          /* Protocol for socket.  */
+    size_t ai_addrlen;        /* Length of socket address.  */
+    struct sockaddr *ai_addr; /* Socket address for socket.  */
+    char *ai_canonname;       /* Canonical name for service location.  */
+    struct addrinfo *ai_next; /* Pointer to next in list.  */
+};
+
+struct addrinfo_args_cache {
+    struct addrinfo **addrinfo_ptr;
+    char node[256];
+};
+// define temporary array for data
+struct bpf_map_def SEC("maps/addrinfo_args_hash") addrinfo_args_hash = {
+    .type = BPF_MAP_TYPE_HASH,
+    .max_entries = 256, // max entries at any time
+    .key_size = sizeof(u32),
+    .value_size = sizeof(struct addrinfo_args_cache),
+};
+
+// BPF output events
+struct bpf_map_def SEC("maps/events") events = {
+    .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
+    .key_size = sizeof(u32),
+    .value_size = sizeof(u32),
+    .max_entries = 256, // max cpus
+};
+
+/**
+ * Hooks gethostbyname calls and emits multiple nameLookupEvent events.
+ * It supports at most MAX_IPS many addresses.
+ */
+SEC("uretprobe/gethostbyname")
+int uretprobe__gethostbyname(struct pt_regs *ctx) {
+    // bpf_tracing_prinkt("Called gethostbyname %d\n",1);
+    struct nameLookupEvent data = {0};
+
+    if (!PT_REGS_RC(ctx))
+        return 0;
+
+    struct hostent *host = (struct hostent *)PT_REGS_RC(ctx);
+    char * hostnameptr = {0};
+    bpf_probe_read(&hostnameptr, sizeof(hostnameptr), &host->h_name);
+    bpf_probe_read_str(&data.host, sizeof(data.host), hostnameptr);
+
+    char **ips = {0};
+    bpf_probe_read(&ips, sizeof(ips), &host->h_addr_list);
+
+#pragma clang loop unroll(full)
+    for (int i = 0; i < MAX_IPS; i++) {
+        char *ip={0};
+        bpf_probe_read(&ip, sizeof(ip), &ips[i]);
+
+        if (ip == NULL) {
+            return 0;
+        }
+        bpf_probe_read_user(&data.addr_type, sizeof(data.addr_type),
+                            &host->h_addrtype);
+
+        if (data.addr_type == AF_INET) {
+            // Only copy the 4 relevant bytes
+            bpf_probe_read_user(&data.ip, 4, ip);
+        } else {
+            bpf_probe_read_user(&data.ip, sizeof(data.ip), ip);
+        }
+
+        bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data,
+                              sizeof(data));
+
+        // char **alias = host->h_aliases;
+        char **aliases = {0};
+        bpf_probe_read(&aliases, sizeof(aliases), &host->h_aliases);
+
+#pragma clang loop unroll(full)
+        for (int j = 0; j < MAX_ALIASES; j++) {
+            char *alias = {0};
+            bpf_probe_read(&alias, sizeof(alias), &aliases[j]);
+
+            if (alias == NULL) {
+                return 0;
+            }
+            bpf_probe_read_user(&data.host, sizeof(data.host), alias);
+            bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data,
+                                  sizeof(data));
+        }
+    }
+
+    return 0;
+}
+
+// capture getaddrinfo call and store the relevant arguments to a hash.
+SEC("uprobe/getaddrinfo")
+int addrinfo(struct pt_regs *ctx) {
+    struct addrinfo_args_cache addrinfo_args = {0};
+    if (!PT_REGS_PARM1(ctx))
+        return 0;
+    if (!PT_REGS_PARM4(ctx))
+        return 0;
+
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u32 tid = (u32)pid_tgid;
+
+    addrinfo_args.addrinfo_ptr = (struct addrinfo **)PT_REGS_PARM4(ctx);
+
+    bpf_probe_read_user_str(&addrinfo_args.node, sizeof(addrinfo_args.node),
+                            (char *)PT_REGS_PARM1(ctx));
+
+    bpf_map_update_elem(&addrinfo_args_hash, &tid, &addrinfo_args,
+                        0 /* flags */);
+
+    return 0;
+}
+
+SEC("uretprobe/getaddrinfo")
+int ret_addrinfo(struct pt_regs *ctx) {
+    struct nameLookupEvent data = {0};
+    struct addrinfo_args_cache *addrinfo_args = {0};
+
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u32 tid = (u32)pid_tgid;
+
+    addrinfo_args = bpf_map_lookup_elem(&addrinfo_args_hash, &tid);
+
+    if (addrinfo_args == 0) {
+        return 0; // missed start
+    }
+
+    struct addrinfo **res_p={0};
+    bpf_probe_read(&res_p, sizeof(res_p), &addrinfo_args->addrinfo_ptr);
+
+#pragma clang loop unroll(full)
+    for (int i = 0; i < MAX_IPS; i++) {
+        struct addrinfo *res={0};
+        bpf_probe_read(&res, sizeof(res), res_p);
+        if (res == NULL) {
+            goto out;
+        }
+        bpf_probe_read(&data.addr_type, sizeof(data.addr_type),
+                       &res->ai_family);
+
+        if (data.addr_type == AF_INET) {
+            struct sockaddr_in *ipv4={0};
+            bpf_probe_read(&ipv4, sizeof(ipv4), &res->ai_addr);
+            // Only copy the 4 relevant bytes
+            bpf_probe_read_user(&data.ip, 4, &ipv4->sin_addr);
+        } else if(data.addr_type == AF_INET6) {
+            struct sockaddr_in6 *ipv6={0};
+            bpf_probe_read(&ipv6, sizeof(ipv6), &res->ai_addr);
+
+            bpf_probe_read_user(&data.ip, sizeof(data.ip), &ipv6->sin6_addr);
+        } else {
+            goto out;
+        }
+
+        bpf_probe_read_kernel_str(&data.host, sizeof(data.host),
+                                  &addrinfo_args->node);
+
+        bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, &data,
+                              sizeof(data));
+
+        struct addrinfo * next={0};
+        bpf_probe_read(&next, sizeof(next), &res->ai_next);
+        if (next == NULL){
+            goto out;
+        }
+        res_p = &next;
+    }
+
+out:
+    bpf_map_delete_elem(&addrinfo_args_hash, &tid);
+
+    return 0;
+}
+
+char _license[] SEC("license") = "GPL";
+u32 _version SEC("version") = 0xFFFFFFFE;
diff --git a/ebpf_prog/opensnitch-procs.c b/ebpf_prog/opensnitch-procs.c
new file mode 100644 (file)
index 0000000..8603618
--- /dev/null
@@ -0,0 +1,205 @@
+#define KBUILD_MODNAME "opensnitch-procs"
+
+#include "common.h"
+
+struct bpf_map_def SEC("maps/proc-events") events = {
+    // Since kernel 4.4
+    .type = BPF_MAP_TYPE_PERF_EVENT_ARRAY,
+    .key_size = sizeof(u32),
+    .value_size = sizeof(u32),
+    .max_entries = 256, // max cpus
+};
+
+struct bpf_map_def SEC("maps/execMap") execMap = {
+       .type = BPF_MAP_TYPE_HASH,
+       .key_size = sizeof(u32),
+       .value_size = sizeof(struct data_t),
+       .max_entries = 256,
+};
+
+
+static __always_inline void new_event(struct data_t* data)
+{
+    // initializing variables with __builtin_memset() is required
+    // for compatibility with bpf on kernel 4.4
+
+    struct task_struct *task;
+    struct task_struct *parent;
+    __builtin_memset(&task, 0, sizeof(task));
+    __builtin_memset(&parent, 0, sizeof(parent));
+    task = (struct task_struct *)bpf_get_current_task();
+    bpf_probe_read(&parent, sizeof(parent), &task->real_parent);
+    data->pid = bpf_get_current_pid_tgid() >> 32;
+
+#if !defined(__arm__) && !defined(__i386__)
+    // on i686 -> invalid read from stack
+    bpf_probe_read(&data->ppid, sizeof(u32), &parent->tgid);
+#endif
+    data->uid = bpf_get_current_uid_gid() & 0xffffffff;
+    bpf_get_current_comm(&data->comm, sizeof(data->comm));
+};
+
+/*
+ * send to userspace the result of the execve* call.
+ */
+static __always_inline void __handle_exit_execve(struct trace_sys_exit_execve *ctx)
+{
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    struct data_t *proc = bpf_map_lookup_elem(&execMap, &pid_tgid);
+    // don't delete the pid from execMap here, delegate it to sched_process_exit
+    if (proc == NULL) { return; }
+    if (ctx->ret != 0) { return; }
+    proc->ret_code = ctx->ret;
+
+    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, proc, sizeof(*proc));
+}
+
+// https://0xax.gitbooks.io/linux-insides/content/SysCall/linux-syscall-4.html
+// bprm_execve REGS_PARM3
+// https://elixir.bootlin.com/linux/latest/source/fs/exec.c#L1796
+
+SEC("tracepoint/sched/sched_process_exit")
+int tracepoint__sched_sched_process_exit(struct pt_regs *ctx)
+{
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    struct data_t *proc = bpf_map_lookup_elem(&execMap, &pid_tgid);
+    // if the pid is not in execMap cache (because it's not of a pid we've
+    // previously intercepted), do not send the event to userspace, because
+    // we won't do anything with it and it consumes CPU cycles (too much in some
+    // scenarios).
+    if (proc == NULL) { return 0; }
+
+    int zero = 0;
+    struct data_t *data = bpf_map_lookup_elem(&heapstore, &zero);
+    if (!data){ return 0; }
+
+    new_event(data);
+    data->type = EVENT_SCHED_EXIT;
+    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
+
+    bpf_map_delete_elem(&execMap, &pid_tgid);
+    return 0;
+};
+
+SEC("tracepoint/syscalls/sys_exit_execve")
+int tracepoint__syscalls_sys_exit_execve(struct trace_sys_exit_execve *ctx)
+{
+    __handle_exit_execve(ctx);
+    return 0;
+};
+
+SEC("tracepoint/syscalls/sys_exit_execveat")
+int tracepoint__syscalls_sys_exit_execveat(struct trace_sys_exit_execve *ctx)
+{
+    __handle_exit_execve(ctx);
+    return 0;
+};
+
+SEC("tracepoint/syscalls/sys_enter_execve")
+int tracepoint__syscalls_sys_enter_execve(struct trace_sys_enter_execve* ctx)
+{
+    int zero = 0;
+    struct data_t *data = {0};
+    data = (struct data_t *)bpf_map_lookup_elem(&heapstore, &zero);
+    if (!data){ return 0; }
+
+    new_event(data);
+    data->type = EVENT_EXEC;
+    // bpf_probe_read_user* helpers were introduced in kernel 5.5
+    // Since the args can be overwritten anyway, maybe we could get them from
+    // mm_struct instead for a wider kernel version support range?
+    bpf_probe_read_user_str(&data->filename, sizeof(data->filename), (const char *)ctx->filename);
+
+    const char *argp={0};
+    data->args_count = 0;
+    data->args_partial = INCOMPLETE_ARGS;
+
+// FIXME: on i386 arch, the following code fails with permission denied.
+#if !defined(__arm__) && !defined(__i386__)
+    #pragma unroll
+    for (int i = 0; i < MAX_ARGS; i++) {
+        bpf_probe_read_user(&argp, sizeof(argp), &ctx->argv[i]);
+        if (!argp){ data->args_partial = COMPLETE_ARGS; break; }
+
+        if (bpf_probe_read_user_str(&data->args[i], MAX_ARG_SIZE, argp) >= MAX_ARG_SIZE){
+            break;
+        }
+        data->args_count++;
+    }
+#endif
+
+// FIXME: on aarch64 we fail to save the event to execMap, so send it to userspace here.
+#if defined(__aarch64__)
+    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
+#else
+    // in case of failure adding the item to the map, send it directly
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+       if (bpf_map_update_elem(&execMap, &pid_tgid, data, BPF_ANY) != 0) {
+
+        // With some commands, this helper fails with error -28 (ENOSPC). Misleading error? cmd failed maybe?
+        // BUG: after coming back from suspend state, this helper fails with error -95 (EOPNOTSUPP)
+        // Possible workaround: count -95 errors, and from userspace reinitialize the streamer if errors >= n-errors
+        bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
+    }
+#endif
+
+    return 0;
+};
+
+SEC("tracepoint/syscalls/sys_enter_execveat")
+int tracepoint__syscalls_sys_enter_execveat(struct trace_sys_enter_execveat* ctx)
+{
+    int zero = 0;
+    struct data_t *data = {0};
+    data = (struct data_t *)bpf_map_lookup_elem(&heapstore, &zero);
+    if (!data){ return 0; }
+
+    new_event((void *)data);
+    data->type = EVENT_EXECVEAT;
+    // bpf_probe_read_user* helpers were introduced in kernel 5.5
+    // Since the args can be overwritten anyway, maybe we could get them from
+    // mm_struct instead for a wider kernel version support range?
+    bpf_probe_read_user_str(&data->filename, sizeof(data->filename), (const char *)ctx->filename);
+
+    const char *argp={0};
+    data->args_count = 0;
+    data->args_partial = INCOMPLETE_ARGS;
+
+// FIXME: on i386 arch, the following code fails with permission denied.
+#if !defined(__arm__) && !defined(__i386__)
+    #pragma unroll
+    for (int i = 0; i < MAX_ARGS; i++) {
+        bpf_probe_read_user(&argp, sizeof(argp), &ctx->argv[i]);
+        if (!argp){ data->args_partial = COMPLETE_ARGS; break; }
+
+        if (bpf_probe_read_user_str(&data->args[i], MAX_ARG_SIZE, argp) >= MAX_ARG_SIZE){
+            break;
+        }
+        data->args_count++;
+    }
+#endif
+
+// FIXME: on aarch64 we fail to save the event to execMap, so send it to userspace here.
+#if defined(__aarch64__)
+    bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
+#else
+    // in case of failure adding the item to the map, send it directly
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+       if (bpf_map_update_elem(&execMap, &pid_tgid, data, BPF_ANY) != 0) {
+
+        // With some commands, this helper fails with error -28 (ENOSPC). Misleading error? cmd failed maybe?
+        // BUG: after coming back from suspend state, this helper fails with error -95 (EOPNOTSUPP)
+        // Possible workaround: count -95 errors, and from userspace reinitialize the streamer if errors >= n-errors
+        bpf_perf_event_output(ctx, &events, BPF_F_CURRENT_CPU, data, sizeof(*data));
+    }
+#endif
+
+    return 0;
+};
+
+
+
+char _license[] SEC("license") = "GPL";
+// this number will be interpreted by the elf loader
+// to set the current running kernel version
+u32 _version SEC("version") = 0xFFFFFFFE;
diff --git a/ebpf_prog/opensnitch.c b/ebpf_prog/opensnitch.c
new file mode 100644 (file)
index 0000000..496619d
--- /dev/null
@@ -0,0 +1,529 @@
+#define KBUILD_MODNAME "dummy"
+
+#include "common_defs.h"
+#include <uapi/linux/tcp.h>
+#include <net/sock.h>
+#include <net/udp_tunnel.h>
+#include <net/inet_sock.h>
+
+struct tcp_key_t {
+    u16 sport;
+    u32 daddr;
+    u16 dport;
+    u32 saddr;
+}__attribute__((packed));
+
+struct tcp_value_t {
+    pid_size_t pid;
+    uid_size_t uid;
+    char comm[TASK_COMM_LEN];
+}__attribute__((packed));
+
+// not using unsigned __int128 because it is not supported on x86_32
+struct ipV6 {
+    u64 part1;
+    u64 part2;
+}__attribute__((packed));
+
+struct tcpv6_key_t {
+    u16 sport;
+    struct ipV6 daddr;
+    u16 dport;
+    struct ipV6 saddr;
+}__attribute__((packed));
+
+struct tcpv6_value_t{
+    pid_size_t pid;
+    uid_size_t uid;
+    char comm[TASK_COMM_LEN];
+}__attribute__((packed));
+
+struct udp_key_t {
+    u16 sport;
+    u32 daddr;
+    u16 dport;
+    u32 saddr;
+} __attribute__((packed));
+
+struct udp_value_t{
+    pid_size_t pid;
+    uid_size_t uid;
+    char comm[TASK_COMM_LEN];
+}__attribute__((packed));
+
+struct udpv6_key_t {
+    u16 sport;
+    struct ipV6 daddr;
+    u16 dport;
+    struct ipV6 saddr;
+}__attribute__((packed));
+
+struct udpv6_value_t{
+    pid_size_t pid;
+    uid_size_t uid;
+    char comm[TASK_COMM_LEN];
+}__attribute__((packed));
+
+
+// on x86_32 "struct sock" is arranged differently from x86_64 (at least on Debian kernels).
+// We hardcode offsets of IP addresses.
+struct sock_on_x86_32_t {
+     u8 data_we_dont_care_about[40];
+     struct ipV6 daddr;
+     struct ipV6 saddr;
+};
+
+
+// Add +1,+2,+3 etc. to map size helps to easier distinguish maps in bpftool's output
+struct bpf_map_def SEC("maps/tcpMap") tcpMap = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(struct tcp_key_t),
+    .value_size = sizeof(struct tcp_value_t),
+    .max_entries = MAPSIZE+1,
+};
+struct bpf_map_def SEC("maps/tcpv6Map") tcpv6Map = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(struct tcpv6_key_t),
+    .value_size = sizeof(struct tcpv6_value_t),
+    .max_entries = MAPSIZE+2,
+};
+struct bpf_map_def SEC("maps/udpMap") udpMap = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(struct udp_key_t),
+    .value_size = sizeof(struct udp_value_t),
+    .max_entries = MAPSIZE+3,
+};
+struct bpf_map_def SEC("maps/udpv6Map") udpv6Map = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(struct udpv6_key_t),
+    .value_size = sizeof(struct udpv6_value_t),
+    .max_entries = MAPSIZE+4,
+};
+
+// for TCP the IP-tuple can be copied from "struct sock" only upon return from tcp_connect().
+// We stash the socket here to look it up upon return.
+struct bpf_map_def SEC("maps/tcpsock") tcpsock = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(u64),
+    // using u64 instead of sizeof(struct sock *)
+    // to avoid pointer size related quirks on x86_32
+    .value_size = sizeof(u64),
+    .max_entries = 300,
+};
+struct bpf_map_def SEC("maps/tcpv6sock") tcpv6sock = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(u64),
+    .value_size = sizeof(u64),
+    .max_entries = 300,
+};
+struct bpf_map_def SEC("maps/icmpsock") icmpsock = {
+    .type = BPF_MAP_TYPE_HASH,
+    .key_size = sizeof(u64),
+    .value_size = sizeof(u64),
+    .max_entries = 300,
+};
+
+
+// initializing variables with __builtin_memset() is required
+// for compatibility with bpf on kernel 4.4
+
+SEC("kprobe/tcp_v4_connect")
+int kprobe__tcp_v4_connect(struct pt_regs *ctx)
+{
+#if defined(__i386__)
+    // On x86_32 platforms I couldn't get function arguments using PT_REGS_PARM1
+    // that's why we are accessing registers directly
+    struct sock *sk = (struct sock *)((ctx)->ax);
+#else
+    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
+#endif
+
+    u64 skp = (u64)sk;
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    bpf_map_update_elem(&tcpsock, &pid_tgid, &skp, BPF_ANY);
+    return 0;
+};
+
+SEC("kretprobe/tcp_v4_connect")
+int kretprobe__tcp_v4_connect(struct pt_regs *ctx)
+{
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u64 *skp = bpf_map_lookup_elem(&tcpsock, &pid_tgid);
+    if (skp == NULL) {return 0;}
+
+    struct sock *sk;
+    __builtin_memset(&sk, 0, sizeof(sk));
+    sk = (struct sock *)*skp;
+
+    struct tcp_key_t tcp_key;
+    __builtin_memset(&tcp_key, 0, sizeof(tcp_key));
+    bpf_probe_read(&tcp_key.dport, sizeof(tcp_key.dport), &sk->__sk_common.skc_dport);
+    bpf_probe_read(&tcp_key.sport, sizeof(tcp_key.sport), &sk->__sk_common.skc_num);
+    bpf_probe_read(&tcp_key.daddr, sizeof(tcp_key.daddr), &sk->__sk_common.skc_daddr);
+    bpf_probe_read(&tcp_key.saddr, sizeof(tcp_key.saddr), &sk->__sk_common.skc_rcv_saddr);
+
+    struct tcp_value_t tcp_value={0};
+    __builtin_memset(&tcp_value, 0, sizeof(tcp_value));
+    tcp_value.pid = pid_tgid >> 32;
+    tcp_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
+    bpf_get_current_comm(&tcp_value.comm, sizeof(tcp_value.comm));
+    bpf_map_update_elem(&tcpMap, &tcp_key, &tcp_value, BPF_ANY);
+
+    bpf_map_delete_elem(&tcpsock, &pid_tgid);
+    return 0;
+};
+
+SEC("kprobe/tcp_v6_connect")
+int kprobe__tcp_v6_connect(struct pt_regs *ctx)
+{
+#if defined(__i386__)
+    struct sock *sk = (struct sock *)((ctx)->ax);
+#else
+    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
+#endif
+
+    u64 skp = (u64)sk;
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    bpf_map_update_elem(&tcpv6sock, &pid_tgid, &skp, BPF_ANY);
+    return 0;
+};
+
+SEC("kretprobe/tcp_v6_connect")
+int kretprobe__tcp_v6_connect(struct pt_regs *ctx)
+{
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u64 *skp = bpf_map_lookup_elem(&tcpv6sock, &pid_tgid);
+    if (skp == NULL) {return 0;}
+
+    struct sock *sk;
+    __builtin_memset(&sk, 0, sizeof(sk));
+    sk = (struct sock *)*skp;
+
+    struct tcpv6_key_t tcpv6_key;
+    __builtin_memset(&tcpv6_key, 0, sizeof(tcpv6_key));
+    bpf_probe_read(&tcpv6_key.dport, sizeof(tcpv6_key.dport), &sk->__sk_common.skc_dport);
+    bpf_probe_read(&tcpv6_key.sport, sizeof(tcpv6_key.sport), &sk->__sk_common.skc_num);
+#if defined(__i386__)
+    struct sock_on_x86_32_t sock;
+    __builtin_memset(&sock, 0, sizeof(sock));
+    bpf_probe_read(&sock, sizeof(sock), *(&sk));
+    tcpv6_key.daddr = sock.daddr;
+    tcpv6_key.saddr = sock.saddr;
+#else
+    bpf_probe_read(&tcpv6_key.daddr, sizeof(tcpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
+    bpf_probe_read(&tcpv6_key.saddr, sizeof(tcpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
+#endif
+
+    struct tcpv6_value_t tcpv6_value={0};
+    __builtin_memset(&tcpv6_value, 0, sizeof(tcpv6_value));
+    tcpv6_value.pid = pid_tgid >> 32;
+    tcpv6_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
+    bpf_get_current_comm(&tcpv6_value.comm, sizeof(tcpv6_value.comm));
+    bpf_map_update_elem(&tcpv6Map, &tcpv6_key, &tcpv6_value, BPF_ANY);
+
+    bpf_map_delete_elem(&tcpv6sock, &pid_tgid);
+    return 0;
+};
+
+SEC("kprobe/udp_sendmsg")
+int kprobe__udp_sendmsg(struct pt_regs *ctx)
+{
+#if defined(__i386__)
+    struct sock *sk = (struct sock *)((ctx)->ax);
+    struct msghdr *msg = (struct msghdr *)((ctx)->dx);
+#else
+    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
+    struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2(ctx);
+#endif
+
+    u64 msg_name; //pointer
+    __builtin_memset(&msg_name, 0, sizeof(msg_name));
+    bpf_probe_read(&msg_name, sizeof(msg_name), &msg->msg_name);
+    struct sockaddr_in * usin = (struct sockaddr_in *)msg_name;
+
+    struct udp_key_t udp_key;
+    __builtin_memset(&udp_key, 0, sizeof(udp_key));
+    bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &usin->sin_port);
+    if (udp_key.dport != 0){ //likely
+        bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &usin->sin_addr.s_addr);
+    }
+    else {
+        //very rarely dport can be found in skc_dport
+        bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &sk->__sk_common.skc_dport);
+        bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &sk->__sk_common.skc_daddr);
+    }
+    bpf_probe_read(&udp_key.sport, sizeof(udp_key.sport), &sk->__sk_common.skc_num);
+    bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &sk->__sk_common.skc_rcv_saddr);
+
+// TODO: armhf
+#if !defined(__arm__)
+    // extract from the ancillary message the source IP.
+    if (udp_key.saddr == 0){
+        u64 cmsg=0;
+        bpf_probe_read(&cmsg, sizeof(cmsg), &msg->msg_control);
+        struct in_pktinfo *inpkt = (struct in_pktinfo *)CMSG_DATA(cmsg);
+        bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &inpkt->ipi_spec_dst.s_addr);
+    }
+#endif
+
+    u32 zero_key = 0;
+    __builtin_memset(&zero_key, 0, sizeof(zero_key));
+    struct udp_value_t *lookedupValue = bpf_map_lookup_elem(&udpMap, &udp_key);
+    u64 pid = bpf_get_current_pid_tgid() >> 32;
+    if (lookedupValue == NULL || lookedupValue->pid != pid) {
+        struct udp_value_t udp_value={0};
+        __builtin_memset(&udp_value, 0, sizeof(udp_value));
+        udp_value.pid = pid;
+        udp_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
+        bpf_get_current_comm(&udp_value.comm, sizeof(udp_value.comm));
+        bpf_map_update_elem(&udpMap, &udp_key, &udp_value, BPF_ANY);
+    }
+    //else nothing to do
+    return 0;
+};
+
+
+SEC("kprobe/udpv6_sendmsg")
+int kprobe__udpv6_sendmsg(struct pt_regs *ctx)
+{
+#if defined(__i386__)
+    struct sock *sk = (struct sock *)((ctx)->ax);
+    struct msghdr *msg = (struct msghdr *)((ctx)->dx);
+#else
+    struct sock *sk = (struct sock *)PT_REGS_PARM1(ctx);
+    struct msghdr *msg = (struct msghdr *)PT_REGS_PARM2(ctx);
+#endif
+
+    u64 msg_name; //a pointer
+    __builtin_memset(&msg_name, 0, sizeof(msg_name));
+    bpf_probe_read(&msg_name, sizeof(msg_name), &msg->msg_name);
+
+    struct udpv6_key_t udpv6_key;
+    __builtin_memset(&udpv6_key, 0, sizeof(udpv6_key));
+    bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sk->__sk_common.skc_dport);
+    if (udpv6_key.dport != 0){ //likely
+        bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
+    }
+    else {
+        struct sockaddr_in6 * sin6 = (struct sockaddr_in6 *)msg_name;
+        bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sin6->sin6_port);
+        bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sin6->sin6_addr.in6_u.u6_addr32);
+    }
+
+    bpf_probe_read(&udpv6_key.sport, sizeof(udpv6_key.sport), &sk->__sk_common.skc_num);
+    bpf_probe_read(&udpv6_key.saddr, sizeof(udpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
+
+    if (udpv6_key.saddr.part1 == 0){
+        u64 cmsg=0;
+        bpf_probe_read(&cmsg, sizeof(cmsg), &msg->msg_control);
+        struct in6_pktinfo *inpkt = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+        bpf_probe_read(&udpv6_key.saddr, sizeof(udpv6_key.saddr), &inpkt->ipi6_addr.s6_addr32);
+    }
+
+#if defined(__i386__)
+    struct sock_on_x86_32_t sock;
+    __builtin_memset(&sock, 0, sizeof(sock));
+    bpf_probe_read(&sock, sizeof(sock), *(&sk));
+    udpv6_key.daddr = sock.daddr;
+    udpv6_key.saddr = sock.saddr;
+#endif
+
+    struct udpv6_value_t *lookedupValue = bpf_map_lookup_elem(&udpv6Map, &udpv6_key);
+    u64 pid = bpf_get_current_pid_tgid() >> 32;
+    if ( lookedupValue == NULL || lookedupValue->pid != pid) {
+        struct udpv6_value_t udpv6_value={0};
+        __builtin_memset(&udpv6_value, 0, sizeof(udpv6_value));
+        bpf_get_current_comm(&udpv6_value.comm, sizeof(udpv6_value.comm));
+        udpv6_value.pid = pid;
+        udpv6_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
+        bpf_map_update_elem(&udpv6Map, &udpv6_key, &udpv6_value, BPF_ANY);
+    }
+    //else nothing to do
+    return 0;
+};
+
+// TODO: armhf
+#if !defined(__arm__)
+SEC("kprobe/inet_dgram_connect")
+int kprobe__inet_dgram_connect(struct pt_regs *ctx)
+{
+#if defined(__i386__)
+    struct socket *skt = (struct socket *)PT_REGS_PARM1(ctx);
+    struct sockaddr *saddr = (struct sockaddr *)PT_REGS_PARM2(ctx);
+#else
+    struct socket *skt = (struct socket *)PT_REGS_PARM1(ctx);
+    struct sockaddr *saddr = (struct sockaddr *)PT_REGS_PARM2(ctx);
+#endif
+
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u64 skp = (u64)skt;
+    u64 sa = (u64)saddr;
+    bpf_map_update_elem(&tcpsock, &pid_tgid, &skp, BPF_ANY);
+    bpf_map_update_elem(&icmpsock, &pid_tgid, &sa, BPF_ANY);
+    return 0;
+}
+
+SEC("kretprobe/inet_dgram_connect")
+int kretprobe__inet_dgram_connect(int retval)
+{
+    u64 pid_tgid = bpf_get_current_pid_tgid();
+    u64 *skp = bpf_map_lookup_elem(&tcpsock, &pid_tgid);
+    if (skp == NULL) { goto out; }
+    u64 *sap = bpf_map_lookup_elem(&icmpsock, &pid_tgid);
+    if (sap == NULL) { goto out; }
+
+    struct sock *sk;
+    struct socket *skt;
+    __builtin_memset(&sk, 0, sizeof(sk));
+    __builtin_memset(&skt, 0, sizeof(skt));
+    skt = (struct socket *)*skp;
+    bpf_probe_read(&sk, sizeof(sk), &skt->sk);
+
+    u8 proto = 0;
+    u8 type = 0;
+    u8 fam = 0;
+    bpf_probe_read(&proto, sizeof(proto), &sk->sk_protocol);
+    bpf_probe_read(&type, sizeof(type), &sk->sk_type);
+    bpf_probe_read(&fam, sizeof(type), &sk->sk_family);
+
+    struct udp_value_t udp_value={0};
+    __builtin_memset(&udp_value, 0, sizeof(udp_value));
+    udp_value.pid = pid_tgid >> 32;
+    udp_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
+    bpf_get_current_comm(&udp_value.comm, sizeof(udp_value.comm));
+
+    if (fam == AF_INET){
+        struct sockaddr_in *ska;
+        struct udp_key_t udp_key;
+        __builtin_memset(&ska, 0, sizeof(ska));
+        __builtin_memset(&udp_key, 0, sizeof(udp_key));
+        ska = (struct sockaddr_in *)*sap;
+
+        bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &ska->sin_addr.s_addr);
+        bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &ska->sin_port);
+        if (udp_key.dport == 0){
+            bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &sk->__sk_common.skc_dport);
+            bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &sk->__sk_common.skc_daddr);
+        }
+        bpf_probe_read(&udp_key.sport, sizeof(udp_key.sport), &sk->__sk_common.skc_num);
+        bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &sk->__sk_common.skc_rcv_saddr);
+
+        udp_key.sport = (udp_key.sport >> 8) | ((udp_key.sport << 8) & 0xff00);
+
+        // There're several reasons for these fields to be empty:
+        // - saddr may be empty if sk_state is 7 (CLOSE)
+        // - <insert more here>
+        if (udp_key.dport == 0 || udp_key.daddr == 0){
+            goto out;
+        }
+
+        if (proto == IPPROTO_UDP){
+            bpf_map_update_elem(&udpMap, &udp_key, &udp_value, BPF_ANY);
+        }
+    } else if (fam == AF_INET6){
+        struct sockaddr_in6 *ska;
+        struct udpv6_key_t udpv6_key;
+        __builtin_memset(&ska, 0, sizeof(ska));
+        __builtin_memset(&udpv6_key, 0, sizeof(udpv6_key));
+        ska = (struct sockaddr_in6 *)*sap;
+
+        bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &sk->__sk_common.skc_dport);
+        if (udpv6_key.dport != 0){ //likely
+            bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &sk->__sk_common.skc_v6_daddr.in6_u.u6_addr32);
+        }
+        else {
+            bpf_probe_read(&udpv6_key.dport, sizeof(udpv6_key.dport), &ska->sin6_port);
+            bpf_probe_read(&udpv6_key.daddr, sizeof(udpv6_key.daddr), &ska->sin6_addr.in6_u.u6_addr32);
+        }
+
+        bpf_probe_read(&udpv6_key.sport, sizeof(udpv6_key.sport), &sk->__sk_common.skc_num);
+        bpf_probe_read(&udpv6_key.saddr, sizeof(udpv6_key.saddr), &sk->__sk_common.skc_v6_rcv_saddr.in6_u.u6_addr32);
+
+#if defined(__i386__)
+        struct sock_on_x86_32_t sock;
+        __builtin_memset(&sock, 0, sizeof(sock));
+        bpf_probe_read(&sock, sizeof(sock), *(&sk));
+        udpv6_key.daddr = sock.daddr;
+        udpv6_key.saddr = sock.saddr;
+#endif
+
+        if (udpv6_key.dport == 0){
+            goto out;
+        }
+
+        if (proto == IPPROTO_UDP){
+            bpf_map_update_elem(&udpv6Map, &udpv6_key, &udp_value, BPF_ANY);
+        }
+    }
+    //if (proto == IPPROTO_UDP && type == SOCK_DGRAM && udp_key.dport == 1025){
+    //    udp_key.dport = 0;
+    //    udp_key.sport = 0;
+    //    bpf_map_update_elem(&icmpMap, &udp_key, &udp_value, BPF_ANY);
+    //}
+    //else if (proto == IPPROTO_UDP && type == SOCK_DGRAM && udp_key.dport != 1025){
+    //    bpf_map_update_elem(&icmpMap, &udp_key, &udp_value, BPF_ANY);
+    //} else if (proto == IPPROTO_TCP && type == SOCK_RAW){
+    //    sport always 6 and dport 0
+    //    bpf_map_update_elem(&tcpMap, &udp_key, &udp_value, BPF_ANY);
+    //}
+
+    return 0;
+out:
+    bpf_map_delete_elem(&tcpsock, &pid_tgid);
+    bpf_map_delete_elem(&icmpsock, &pid_tgid);
+
+    return 0;
+};
+#endif
+
+// TODO: for 32bits
+#if !defined(__arm__) && !defined(__i386__)
+
+SEC("kprobe/iptunnel_xmit")
+int kprobe__iptunnel_xmit(struct pt_regs *ctx)
+{
+    struct sk_buff *skb = (struct sk_buff *)PT_REGS_PARM3(ctx);
+    u32 src = (u32)PT_REGS_PARM4(ctx);
+    u32 dst = (u32)PT_REGS_PARM5(ctx);
+
+    u16 sport = 0;
+    unsigned char *head;
+    u16 pkt_hdr;
+    __builtin_memset(&head, 0, sizeof(head));
+    __builtin_memset(&pkt_hdr, 0, sizeof(pkt_hdr));
+    bpf_probe_read(&head, sizeof(head), &skb->head);
+    bpf_probe_read(&pkt_hdr, sizeof(pkt_hdr), &skb->transport_header);
+    struct udphdr *udph;
+    __builtin_memset(&udph, 0, sizeof(udph));
+
+    udph = (struct udphdr *)(head + pkt_hdr);
+    bpf_probe_read(&sport, sizeof(sport), &udph->source);
+    sport = (sport >> 8) | ((sport << 8) & 0xff00);
+
+    struct udp_key_t udp_key;
+    struct udp_value_t udp_value;
+    __builtin_memset(&udp_key, 0, sizeof(udp_key));
+    __builtin_memset(&udp_value, 0, sizeof(udp_value));
+
+    bpf_probe_read(&udp_key.sport, sizeof(udp_key.sport), &sport);
+    bpf_probe_read(&udp_key.dport, sizeof(udp_key.dport), &udph->dest);
+    bpf_probe_read(&udp_key.saddr, sizeof(udp_key.saddr), &src);
+    bpf_probe_read(&udp_key.daddr, sizeof(udp_key.daddr), &dst);
+
+    struct udp_value_t *lookedupValue = bpf_map_lookup_elem(&udpMap, &udp_key);
+    u64 pid = bpf_get_current_pid_tgid() >> 32;
+    if ( lookedupValue == NULL || lookedupValue->pid != pid) {
+        bpf_get_current_comm(&udp_value.comm, sizeof(udp_value.comm));
+        udp_value.pid = pid;
+        udp_value.uid = bpf_get_current_uid_gid() & 0xffffffff;
+        bpf_map_update_elem(&udpMap, &udp_key, &udp_value, BPF_ANY);
+    }
+
+    return 0;
+};
+#endif
+
+char _license[] SEC("license") = "GPL";
+// this number will be interpreted by the elf loader
+// to set the current running kernel version
+u32 _version SEC("version") = 0xFFFFFFFE;
diff --git a/proto/.gitignore b/proto/.gitignore
new file mode 100644 (file)
index 0000000..0d20b64
--- /dev/null
@@ -0,0 +1 @@
+*.pyc
diff --git a/proto/Makefile b/proto/Makefile
new file mode 100644 (file)
index 0000000..a58241c
--- /dev/null
@@ -0,0 +1,13 @@
+all: ../daemon/ui/protocol/ui.pb.go ../ui/opensnitch/ui_pb2.py
+
+../daemon/ui/protocol/ui.pb.go: ui.proto
+       protoc -I. ui.proto --go_out=../daemon/ui/protocol/ --go-grpc_out=../daemon/ui/protocol/ --go_opt=paths=source_relative --go-grpc_opt=paths=source_relative
+
+../ui/opensnitch/ui_pb2.py: ui.proto
+       python3 -m grpc_tools.protoc -I. --python_out=../ui/opensnitch/ --grpc_python_out=../ui/opensnitch/ ui.proto
+
+clean:
+       @rm -rf ../daemon/ui/protocol/ui.pb.go
+       @rm -rf ../daemon/ui/protocol/ui_grpc.pb.go
+       @rm -rf ../ui/opensnitch/ui_pb2.py
+       @rm -rf ../ui/opensnitch/ui_pb2_grpc.py
diff --git a/proto/ui.proto b/proto/ui.proto
new file mode 100644 (file)
index 0000000..2c5b3c0
--- /dev/null
@@ -0,0 +1,264 @@
+syntax = "proto3";
+
+package protocol;
+
+option go_package = "github.com/evilsocket/opensnitch/daemon/ui/protocol";
+
+service UI {
+    rpc Ping(PingRequest) returns (PingReply) {}
+    rpc AskRule (Connection) returns (Rule) {}
+    rpc Subscribe (ClientConfig) returns (ClientConfig) {}
+    rpc Notifications (stream NotificationReply) returns (stream Notification) {}
+    rpc PostAlert(Alert) returns (MsgResponse) {}
+}
+
+/**
+  - Send error messages (kernel not compatible, etc)
+  - Send warnings (eBPF modules failed loading, etc)
+  - Send kernel events: new execs, bytes recv/sent, ...
+  - Alert of events defined by the user: alert when a rule matches
+*/
+message Alert {
+    enum Priority {
+        LOW = 0;
+        MEDIUM = 1;
+        HIGH = 2;
+    }
+    enum Type {
+        ERROR = 0;
+        WARNING = 1;
+        INFO = 2;
+    }
+    enum Action {
+        NONE = 0;
+        SHOW_ALERT = 1;
+        SAVE_TO_DB = 2;
+    }
+    // What caused the alert
+    enum What {
+        GENERIC = 0;
+        PROC_MONITOR = 1;
+        FIREWALL = 2;
+        CONNECTION = 3;
+        RULE = 4;
+        NETLINK = 5;
+        // bind, exec, etc
+        KERNEL_EVENT = 6;
+    }
+
+    uint64 id = 1;
+    Type type = 2;
+    // TODO: group of actions: SHOW_ALERT | SAVE_TO_DB
+    Action action = 3;
+    Priority priority = 4;
+    What what = 5;
+    // https://developers.google.com/protocol-buffers/docs/reference/go-generated#oneof
+    oneof data {
+        // errors, messages, etc
+        string text = 6;
+        // proc events: send/recv bytes, etc
+        Process proc = 8;
+        // conn events: bind, listen, etc
+        Connection conn = 9;
+        Rule rule = 10;
+        FwRule fwrule = 11;
+    }
+}
+
+message MsgResponse {
+    uint64 id = 1;
+}
+
+message Event {
+    string time = 1;
+    Connection connection = 2;
+    Rule rule = 3;
+    int64 unixnano = 4;
+}
+
+message Statistics {
+    string daemon_version = 1;
+    uint64 rules = 2;
+    uint64 uptime = 3;
+       uint64 dns_responses = 4;
+       uint64 connections  = 5;
+       uint64 ignored = 6;
+       uint64 accepted = 7;
+       uint64 dropped = 8;
+       uint64 rule_hits = 9;
+       uint64 rule_misses = 10;
+       map<string, uint64> by_proto = 11;
+       map<string, uint64> by_address = 12;
+       map<string, uint64> by_host = 13;
+       map<string, uint64> by_port = 14;
+       map<string, uint64> by_uid = 15;
+       map<string, uint64> by_executable = 16;
+    repeated Event events = 17;
+}
+
+message PingRequest {
+    uint64 id = 1;
+    Statistics stats = 2;
+}
+
+message PingReply {
+    uint64 id = 1;
+}
+
+message Process {
+    uint64 pid = 1;
+    uint64 ppid = 2;
+    uint64 uid = 3;
+    string comm = 4;
+    string path = 5;
+    repeated string args = 6;
+    map<string, string> env = 7;
+    string cwd = 8;
+    uint64 io_reads = 9;
+    uint64 io_writes = 10;
+    uint64 net_reads = 11;
+    uint64 net_writes = 12;
+}
+
+message Connection {
+    string protocol = 1;
+    string src_ip = 2;
+    uint32 src_port = 3;
+    string dst_ip = 4;
+    string dst_host = 5;
+    uint32 dst_port = 6;
+    uint32 user_id = 7;
+    uint32 process_id = 8;
+    string process_path = 9;
+    string process_cwd = 10;
+    repeated string process_args = 11;
+    map<string, string> process_env = 12;
+}
+
+message Operator {
+    string type = 1;
+    string operand = 2;
+    string data = 3;
+    bool sensitive = 4;
+    repeated Operator list = 5;
+}
+
+message Rule {
+    int64 created = 1;
+    string name = 2;
+    string description = 3;
+    bool enabled = 4;
+    bool precedence = 5;
+    bool nolog = 6;
+    string action = 7;
+    string duration = 8;
+    Operator operator = 9;
+}
+
+enum Action {
+    NONE = 0;
+    ENABLE_INTERCEPTION = 1;
+    DISABLE_INTERCEPTION = 2;
+    ENABLE_FIREWALL = 3;
+    DISABLE_FIREWALL = 4;
+    RELOAD_FW_RULES = 5;
+    CHANGE_CONFIG = 6;
+    ENABLE_RULE = 7;
+    DISABLE_RULE = 8;
+    DELETE_RULE = 9;
+    CHANGE_RULE = 10;
+    LOG_LEVEL = 11;
+    STOP = 12;
+    MONITOR_PROCESS = 13;
+    STOP_MONITOR_PROCESS = 14;
+}
+
+message StatementValues {
+    string Key = 1;
+    string Value = 2;
+}
+
+message Statement {
+    string Op = 1;
+    string Name = 2;
+    repeated StatementValues Values = 3;
+}
+
+message Expressions {
+    Statement Statement = 1;
+}
+
+message FwRule {
+    // DEPRECATED: for backward compatibility with iptables
+    string Table = 1;
+    string Chain = 2;
+
+    string UUID = 3;
+    bool Enabled = 4;
+    uint64 Position = 5;
+    string Description = 6;
+    string Parameters = 7;
+    repeated Expressions Expressions = 8;
+    string Target = 9;
+    string TargetParameters = 10;
+}
+
+message FwChain {
+    string Name = 1;
+    string Table = 2;
+    string Family = 3;
+    string Priority = 4;
+    string Type = 5;
+    string Hook = 6;
+    string Policy = 7;
+    repeated FwRule Rules = 8;
+}
+
+message FwChains {
+    // DEPRECATED: backward compatibility with iptables
+    FwRule Rule = 1;
+    repeated FwChain Chains = 2;
+}
+
+message SysFirewall {
+    bool Enabled = 1;
+    uint32 Version = 2;
+    repeated FwChains SystemRules = 3;
+}
+
+// client configuration sent on Subscribe()
+message ClientConfig {
+    uint64 id = 1;
+    string name = 2;
+    string version = 3;
+    bool isFirewallRunning = 4;
+    // daemon configuration as json string
+    string config = 5;
+    uint32 logLevel = 6;
+    repeated Rule rules = 7;
+    SysFirewall systemFirewall = 8;
+}
+
+// notification sent to the clients (daemons)
+message Notification {
+    uint64 id = 1;
+    string clientName = 2;
+    string serverName = 3;
+    // CHANGE_CONFIG: 2, data: {"default_timeout": 1, ...}
+    Action type = 4;
+    string data = 5;   
+    repeated Rule rules = 6;
+    SysFirewall sysFirewall = 7;
+}
+
+// notification reply sent to the server (GUI)
+message NotificationReply {
+    uint64 id = 1;
+    NotificationReplyCode code = 2;
+    string data = 3;
+}
+
+enum NotificationReplyCode {
+    OK = 0;
+    ERROR = 1;
+}
diff --git a/release.sh b/release.sh
new file mode 100755 (executable)
index 0000000..d282411
--- /dev/null
@@ -0,0 +1,28 @@
+#!/bin/bash
+# nothing to see here, just a utility i use to create new releases ^_^
+
+CURRENT_VERSION=$(cat daemon/core/version.go | grep Version | cut -d '"' -f 2)
+TO_UPDATE=(
+    daemon/core/version.go
+    ui/version.py 
+)
+
+echo -n "Current version is $CURRENT_VERSION, select new version: "
+read NEW_VERSION
+echo "Creating version $NEW_VERSION ...\n"
+
+for file in "${TO_UPDATE[@]}"
+do
+    echo "Patching $file ..."
+    sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" $file
+    git add $file
+done
+
+git commit -m "Releasing v$NEW_VERSION"
+git push
+
+git tag -a v$NEW_VERSION -m "Release v$NEW_VERSION"
+git push origin v$NEW_VERSION
+
+echo
+echo "All done, v$NEW_VERSION released ^_^"
diff --git a/screenshots/opensnitch-ui-general-tab-deny.png b/screenshots/opensnitch-ui-general-tab-deny.png
new file mode 100644 (file)
index 0000000..809fc8e
Binary files /dev/null and b/screenshots/opensnitch-ui-general-tab-deny.png differ
diff --git a/screenshots/opensnitch-ui-proc-details.png b/screenshots/opensnitch-ui-proc-details.png
new file mode 100644 (file)
index 0000000..1190a29
Binary files /dev/null and b/screenshots/opensnitch-ui-proc-details.png differ
diff --git a/screenshots/screenshot.png b/screenshots/screenshot.png
new file mode 100644 (file)
index 0000000..5e8c5ab
Binary files /dev/null and b/screenshots/screenshot.png differ
diff --git a/ui/.gitignore b/ui/.gitignore
new file mode 100644 (file)
index 0000000..a244087
--- /dev/null
@@ -0,0 +1,5 @@
+*.pyc
+build
+dist
+*.egg-info
+__pycache__
diff --git a/ui/LICENSE b/ui/LICENSE
new file mode 100644 (file)
index 0000000..0b86ea0
--- /dev/null
@@ -0,0 +1,28 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Source: https://github.com/gustavo-iniguez-goya/opensnitch
+Upstream-Name: python3-opensnitch-ui
+Files: *
+Copyright:
+ 2017-2018 evilsocket
+ 2019-2020 Gustavo Iñiguez Goia
+Comment: Debian packaging is licensed under the same terms as upstream
+License: GPL-3.0
+ This program is free software; you can redistribute it
+ and/or modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later
+ version.
+ .
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE.  See the GNU General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU General Public
+ License along with this program. If not,  If not, see 
+ http://www.gnu.org/licenses/.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ '/usr/share/common-licenses/GPL-3'.
diff --git a/ui/MANIFEST.in b/ui/MANIFEST.in
new file mode 100644 (file)
index 0000000..d21350d
--- /dev/null
@@ -0,0 +1,5 @@
+recursive-include opensnitch/proto *
+recursive-include opensnitch/res *
+recursive-include opensnitch/i18n *.qm
+recursive-include opensnitch/database/migrations *.sql
+include LICENSE
diff --git a/ui/Makefile b/ui/Makefile
new file mode 100644 (file)
index 0000000..0eeda85
--- /dev/null
@@ -0,0 +1,19 @@
+all: opensnitch/resources_rc.py
+
+install:
+       @pip3 install --upgrade .
+
+opensnitch/resources_rc.py: translations deps
+       @pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc
+       @find opensnitch/proto/ -name 'ui_pb2_grpc.py' -exec sed -i 's/^import ui_pb2/from . import ui_pb2/' {} \;
+
+translations:
+       @cd i18n ; make
+       
+deps:
+       @pip3 install -r requirements.txt
+
+clean:
+       @rm -rf *.pyc
+       @rm -rf opensnitch/resources_rc.py
+       @find i18n/ -name '*.qm' -delete
diff --git a/ui/bin/opensnitch-ui b/ui/bin/opensnitch-ui
new file mode 100755 (executable)
index 0000000..06e52ae
--- /dev/null
@@ -0,0 +1,261 @@
+#!/usr/bin/env python3
+
+#   Copyright (C) 2018      Simone Margaritelli
+#                 2018      MiWCryptAnalytics
+#                 2023      munix9
+#                 2023      Wojtek Widomski
+#                 2019-2023 Gustavo Iñiguez Goia
+#
+#   This file is part of OpenSnitch.
+#
+#   OpenSnitch is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   OpenSnitch is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with OpenSnitch.  If not, see <http://www.gnu.org/licenses/>.
+
+from PyQt5 import QtWidgets, QtCore
+from PyQt5.QtNetwork import QLocalServer, QLocalSocket
+
+import sys
+import os
+import signal
+import argparse
+import logging
+
+from concurrent import futures
+
+import grpc
+
+dist_path = '/usr/lib/python3/dist-packages/'
+if dist_path not in sys.path:
+    sys.path.append(dist_path)
+
+from opensnitch.service import UIService
+from opensnitch.config import Config
+from opensnitch.utils import Themes, Utils, Versions, Message
+from opensnitch.utils.xdg import xdg_opensnitch_dir, xdg_current_session
+
+from opensnitch import auth
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+app_id = os.path.join(xdg_opensnitch_dir, "io.github.evilsocket.opensnitch")
+
+def on_exit():
+    server.stop(0)
+    app.quit()
+    try:
+        os.remove(app_id)
+    except:
+        pass
+    sys.exit(0)
+
+def restrict_socket_perms(socket):
+    """Restrict socket reading to the current user"""
+    try:
+        if socket.startswith("unix://") and os.path.exists(socket[7:]):
+            os.chmod(socket[7:], 0o640)
+    except Exception as e:
+        print("Unable to change unix socket permissions:", socket, e)
+
+def configure_screen_scale_factor(cfg):
+    """configure qt screen scale:
+        https://doc.qt.io/qt-5/highdpi.html#high-dpi-support-in-qt
+    """
+    auto_screen_factor = cfg.getBool(Config.QT_AUTO_SCREEN_SCALE_FACTOR, default_value=True)
+    screen_factor = cfg.getSettings(Config.QT_SCREEN_SCALE_FACTOR)
+    if screen_factor is None or screen_factor == "":
+        screen_factor = "1"
+
+    print("QT_AUTO_SCREEN_SCALE_FACTOR:", auto_screen_factor)
+    os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = str(int(auto_screen_factor))
+    if auto_screen_factor is False:
+        print("QT_SCREEN_SCALE_FACTORS:", screen_factor)
+        os.environ["QT_SCREEN_SCALE_FACTORS"] = screen_factor
+
+def configure_qt_platform_plugin(cfg):
+    qt_plugin = cfg.getSettings(Config.QT_PLATFORM_PLUGIN)
+    if qt_plugin is None or qt_plugin == "":
+        return
+
+    print("QT_QPA_PLATFORM:", qt_plugin)
+    os.environ["QT_QPA_PLATFORM"] = qt_plugin
+
+def check_environ():
+    if xdg_current_session == "":
+        print("""
+
+  Warning: XDG_SESSION_TYPE is not set.
+    If there're no icons on the GUI, please, read the following comment:
+    https://github.com/evilsocket/opensnitch/discussions/999#discussioncomment-6579273
+
+""")
+
+def supported_qt_version(major, medium, minor):
+    q = QtCore.QT_VERSION_STR.split(".")
+    return int(q[0]) >= major and int(q[1]) >= medium and int(q[2]) >= minor
+
+if __name__ == '__main__':
+    gui_version, grpcversion, protoversion = Versions.get()
+    print("\t ~ OpenSnitch GUI -", gui_version, "~")
+    print("\tprotobuf:", protoversion, "-", "grpc:", grpcversion)
+    print("-" * 50, "\n")
+
+    parser = argparse.ArgumentParser(description='OpenSnitch UI service.', formatter_class=argparse.RawTextHelpFormatter)
+    parser.add_argument("--socket", dest="socket", help='''
+Path of the unix socket for the gRPC service (https://github.com/grpc/grpc/blob/master/doc/naming.md).
+Default: unix:///tmp/osui.sock
+
+Examples:
+    - Listening on Unix socket: opensnitch-ui --socket unix:///tmp/osui.sock
+        * Use unix:///run/1000/YOUR_USER/opensnitch/osui.sock for better privacy.
+    - Listening on port 50051, all interfaces: opensnitch-ui --socket "[::]:50051"
+                        ''', metavar="FILE")
+    parser.add_argument("--socket-auth", dest="socket_auth", help="Auth type: simple, tls-simple, tls-mutual")
+    parser.add_argument("--tls-ca-cert", dest="tls_ca_cert", help="path to the CA cert")
+    parser.add_argument("--tls-cert", dest="tls_cert", help="path to the server cert")
+    parser.add_argument("--tls-key", dest="tls_key", help="path to the server key")
+    parser.add_argument("--max-clients", dest="serverWorkers", default=10, help="Max number of allowed clients (incoming connections).")
+    parser.add_argument("--debug", dest="debug", action="store_true", help="Enable debug logs")
+    parser.add_argument("--debug-grpc", dest="debug_grpc", action="store_true", help="Enable gRPC debug logs")
+    parser.add_argument("--background", dest="background", action="store_true", help="Start UI in background even, when tray is not available")
+
+    args = parser.parse_args()
+
+    if args.debug:
+        import faulthandler
+        faulthandler.enable()
+
+    logging.getLogger().disabled = not args.debug
+    cfg = Config.get()
+    configure_screen_scale_factor(cfg)
+    configure_qt_platform_plugin(cfg)
+
+    if args.debug and args.debug_grpc:
+        os.environ["GRPC_TRACE"] = "all"
+        os.environ["GRPC_VERBOSITY"] = "debug"
+
+    if supported_qt_version(5,6,0):
+        try:
+            # NOTE: maybe we also need Qt::AA_UseHighDpiPixmaps
+            QtCore.QApplication.setAttribute(QtCore.Qt.AA_EnableHighDpiScaling, True)
+        except Exception:
+            pass
+
+    service = None
+
+    try:
+        Utils.create_socket_dirs()
+        app = QtWidgets.QApplication(sys.argv)
+
+        localsocket = QLocalSocket()
+        localsocket.connectToServer(app_id)
+
+        if localsocket.waitForConnected():
+            raise Exception("GUI already running, opening its window and exiting.")
+        else:
+            localserver = QLocalServer()
+            localserver.setSocketOptions(QLocalServer.UserAccessOption)
+            localserver.removeServer(app_id)
+            localserver.listen(app_id)
+
+        if hasattr(QtCore.Qt, 'AA_UseHighDpiPixmaps'):
+            app.setAttribute(QtCore.Qt.AA_UseHighDpiPixmaps, True)
+        thm = Themes.instance()
+        thm.load_theme(app)
+
+        if args.socket == None:
+            # default
+            args.socket = "unix:///tmp/osui.sock"
+
+            addr = cfg.getSettings(Config.DEFAULT_SERVER_ADDR)
+            if addr != None and addr != "":
+                if addr.startswith("unix://"):
+                    if not os.path.exists(os.path.dirname(addr[7:])):
+                        print("WARNING: unix socket path does not exist, using unix:///tmp/osui.sock, ", addr)
+                    else:
+                        args.socket = addr
+                else:
+                    args.socket = addr
+
+        maxmsglen = cfg.getMaxMsgLength()
+
+        service = UIService(app, on_exit, start_in_bg=args.background)
+        check_environ()
+        localserver.newConnection.connect(service.OpenWindow)
+        # @doc: https://grpc.github.io/grpc/python/grpc.html#server-object
+        server = grpc.server(futures.ThreadPoolExecutor(),
+                                options=(
+                                    # https://github.com/grpc/grpc/blob/master/doc/keepalive.md
+                                    # https://grpc.github.io/grpc/core/group__grpc__arg__keys.html
+                                    # send keepalive ping every 5 second, default is 2 hours)
+                                    ('grpc.keepalive_time_ms', 5000),
+                                    # after 5s of inactivity, wait 20s and close the connection if
+                                    # there's no response.
+                                    ('grpc.keepalive_timeout_ms', 20000),
+                                    ('grpc.keepalive_permit_without_calls', True),
+                                    ('grpc.max_send_message_length', maxmsglen),
+                                    ('grpc.max_receive_message_length', maxmsglen),
+                                ))
+
+        ui_pb2_grpc.add_UIServicer_to_server(service, server)
+
+        auth_type = auth.Simple
+        if args.socket_auth != None:
+            auth_type = args.socket_auth
+        elif cfg.getSettings(Config.AUTH_TYPE) != None:
+            auth_type = cfg.getSettings(Config.AUTH_TYPE)
+
+        # grpc python doesn't seem to accept unix:@address to listen on an
+        # abstract unix socket, so use unix-abstract: and transform it to what
+        # the Go client understands.
+        if args.socket.startswith("unix:@"):
+            parts = args.socket.split("@")
+            args.socket = "unix-abstract:{0}".format(parts[1])
+
+        print("Using server address:", args.socket, "auth type:", auth_type)
+
+        if auth_type == auth.Simple or auth_type == "":
+            server.add_insecure_port(args.socket)
+        else:
+            auth_ca_cert = args.tls_ca_cert
+            auth_cert = args.tls_cert
+            auth_certkey = args.tls_key
+            if auth_cert == None:
+                auth_cert = cfg.getSettings(Config.AUTH_CERT)
+            if auth_certkey == None:
+                auth_certkey = cfg.getSettings(Config.AUTH_CERTKEY)
+            if auth_ca_cert == None:
+                auth_ca_cert = cfg.getSettings(Config.AUTH_CA_CERT)
+
+            tls_creds = auth.get_tls_credentials(auth_ca_cert, auth_cert, auth_certkey)
+            if tls_creds == None:
+                raise Exception("Invalid TLS credentials. Review the server key and cert files.")
+            server.add_secure_port(args.socket, tls_creds)
+
+        # https://stackoverflow.com/questions/5160577/ctrl-c-doesnt-work-with-pyqt
+        signal.signal(signal.SIGINT, signal.SIG_DFL)
+
+        # print "OpenSnitch UI service running on %s ..." % socket
+        server.start()
+
+        restrict_socket_perms(args.socket)
+
+        app.exec_()
+
+    except KeyboardInterrupt:
+        on_exit()
+    except Exception as e:
+        print(e)
+    finally:
+        if service:
+            # finish gracefully, closing notifications channel.
+            service.close()
diff --git a/ui/i18n/Makefile b/ui/i18n/Makefile
new file mode 100644 (file)
index 0000000..2706005
--- /dev/null
@@ -0,0 +1,37 @@
+SOURCES +=  ../opensnitch/service.py \
+           ../opensnitch/dialogs/prompt.py \
+           ../opensnitch/dialogs/preferences.py \
+           ../opensnitch/dialogs/ruleseditor.py \
+           ../opensnitch/dialogs/processdetails.py \
+           ../opensnitch/dialogs/stats.py
+
+FORMS += ../opensnitch/res/prompt.ui \
+           ../opensnitch/res/ruleseditor.ui \
+           ../opensnitch/res/preferences.ui \
+           ../opensnitch/res/process_details.ui \
+           ../opensnitch/res/stats.ui
+
+#TSFILES contains all *.ts files in locales/ and its subfolders 
+TSFILES := $(shell find locales/ -type f -name '*.ts')
+#QMFILES contains all *.qm files in locales/ and its subfolders 
+QMFILES := $(shell find locales/ -type f -name '*.qm')
+#if QMFILES is empty, we set it to phony target to run unconditionally
+ifeq ($(QMFILES),)
+QMFILES := "qmfiles"
+endif
+
+all: $(TSFILES) $(QMFILES)
+
+#if any file from SOURCES or FORMS is older than any file from $(TSFILES)  
+#or if opensnitch_i18n.pro was manually modified
+$(TSFILES): $(SOURCES) $(FORMS) opensnitch_i18n.pro
+       @pylupdate5 opensnitch_i18n.pro
+
+#if any of the *.ts files are older that any of the *.qm files
+#QMFILES may also be a phony target (when no *.qm exist yet) which will always run
+$(QMFILES):$(TSFILES)
+       @./generate_i18n.sh
+       for lang in $$(ls locales/); do \
+               if [ ! -d ../opensnitch/i18n/$$lang ]; then mkdir -p ../opensnitch/i18n/$$lang ; fi ; \
+               cp locales/$$lang/opensnitch-$$lang.qm ../opensnitch/i18n/$$lang/ ; \
+       done
diff --git a/ui/i18n/README.md b/ui/i18n/README.md
new file mode 100644 (file)
index 0000000..c10f80d
--- /dev/null
@@ -0,0 +1,37 @@
+
+### Adding a new translation:
+0. Install needed packages: `apt install qtchooser pyqt5-dev-tools`
+1. mkdir `locales/<YOUR LOCALE>/`
+ (echo $LANG)
+2. add the path to opensnitch_i18n.pro:
+```
+  TRANSLATIONS += locales/es_ES/opensnitch-es_ES.ts \
+                  locales/<YOUR LOCALE>/opensnitch-<YOUR LOCALE>.ts
+```
+3. make
+
+### Updating translations:
+
+1. update translations definitions:
+ - pylupdate5 opensnitch_i18n.pro
+
+2. translate a language:
+ - linguist locales/es_ES/opensnitch-es_ES.ts
+
+3. create .qm file:
+ - lrelease locales/es_ES/opensnitch-es_ES.ts -qm locales/es_ES/opensnitch-es_ES.qm
+
+or:
+
+1. make
+2. linguist locales/es_ES/opensnitch-es_ES.ts
+3. make
+
+### Installing translations (manually)
+
+In order to test a new translation:
+
+`mkdir -p /usr/lib/python3/dist-packages/opensnitch/i18n/<YOUR LOCALE>/`
+`cp locales/<YOUR LOCALE>/opensnitch-<YOUR LOCALE>.qm /usr/lib/python3/dist-packages/opensnitch/i18n/<YOUR LOCALE>/`
+
+Note: the destination path may vary depending on your system.
diff --git a/ui/i18n/generate_i18n.sh b/ui/i18n/generate_i18n.sh
new file mode 100755 (executable)
index 0000000..0fbbe71
--- /dev/null
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+app_name="opensnitch"
+langs_dir="./locales"
+lrelease="lrelease"
+if ! command -v lrelease >/dev/null; then
+    # on fedora
+    lrelease="lrelease-qt5"
+fi
+
+#pylupdate5 opensnitch_i18n.pro
+
+for lang in $(ls $langs_dir)
+do
+    lang_path="$langs_dir/$lang/$app_name-$lang"
+    $lrelease $lang_path.ts -qm $lang_path.qm
+done
diff --git a/ui/i18n/locales/de_DE/opensnitch-de_DE.ts b/ui/i18n/locales/de_DE/opensnitch-de_DE.ts
new file mode 100644 (file)
index 0000000..bd717dd
--- /dev/null
@@ -0,0 +1,3345 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="de_DE">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>OpenSnitch Firewall</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>User ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Ausgeführt von&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Quell-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>Prozess ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Ziel-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Zielport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="150"/>
+        <source>Chromium Web Browser</source>
+        <translation type="obsolete">Chromium-Webbrowser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="226"/>
+        <source>(/path/to/bin/chromium)</source>
+        <translation type="obsolete">(Pfad/zur/bin/chromium)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="271"/>
+        <source>Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</source>
+        <translation type="obsolete">Der Chromium-Webbrowser möchte eine Verbindung zu www.evilsocket.net über TCP-Port 443 herstellen. Und möglicherweise zu www.goodsocket.net über Port 344</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>von dieser ausführbaren Datei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>von dieser Kommandozeile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>dieser Zielport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>dieser Benutzer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>diese Ziel-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>einmal</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">für diese Sitzung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>für immer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation type="unfinished">Verweigern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Erlauben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished">Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished">Aktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished">Aktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished">Löschen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished">Speichern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Einstellungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Popup-Fenster</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dieses Zeitlimit ist der Countdown, der angezeigt wird, wenn ein Popup-Fenster angezeigt wird.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Standardzeitlimit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Popup-Standarddauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Standarddauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Popup-Standardaktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Standardaktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Standardfilterung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>mittig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>oben rechts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>unten rechts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>oben links</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>unten links</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Grundposition</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>nach ausführbarer Datei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>nach Befehl</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>nach Zielport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>nach Ziel-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>nach UID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>einmal</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">für diese Sitzung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>für immer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>verweigern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>erlauben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Popups deaktivieren, nur eine Warnung anzeigen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Prozessüberwachungsmethode</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Die Standarddauer gilt, wenn keine Benutzeroberfläche verbunden ist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Knotenadresse.&lt;/p&gt;&lt;p&gt;Standardmäßig: unix: ///tmp/osui.sock (unix: // ist erforderlich, wenn ein Unix-Socket vorhanden ist)&lt;/p&gt;&lt;p&gt;Es kann sich auch um eine IP mit diesem Format handeln: 127.0.0.1:50051, 192.168.1.122:12345 usw.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Standardprotokollstufe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Die Standardaktion wird angewendet, wenn keine Benutzeroberfläche verbunden ist.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Protokolldatei, in welche die Protokolle geschrieben werden sollen.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout schreibt die Protokolle in die Standardausgabe des Dienstes..&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Logdatei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Wenn Sie diese Option aktivieren, werden Sie von OpenSnitch aufgefordert, Verbindungen zu akzeptieren oder zu verweigern, denen aus verschiedenen Gründen keine PID zugeordnet ist.
+
+Das Popup-Fenster enthält nur Informationen zur Verbindung.
+
+Hinweis: Diese Verbindungen müssen nicht darauf hinweisen, dass etwas Verdächtiges passiert. Einfach
+ist, dass wir die PID nicht entdeckt haben (zum Beispiel Verbindungen, die nicht vom Computer stammen, oder fehlerhafte Pakete).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Unbekannte Verbindungen abfangen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>HostName</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>immer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Konfiguration auf alle Knoten anwenden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Datenbank</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="630"/>
+        <source>Database name</source>
+        <translation type="obsolete">Datenbankname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>Im Speicher</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Datei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>/path/to/the/file.db</source>
+        <translation type="obsolete">/Pfad/zu/der/Datei.db</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Schließen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Anwenden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Speichern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>Bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>In der erweiterten Ansicht können Sie ganz einfach mehrere Felder auswählen, um Verbindungen zu filtern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Standardmäßig erweiterte Ansicht anzeigen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Aktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Wenn diese Option aktiviert ist, werden die Pop-ups mit aktiver erweiterter Ansicht angezeigt.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Dauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Wenn ein neues Popup-Fenster in seiner einfachsten Form angezeigt wird, können Sie standardmäßig Verbindungen oder Anwendungen nach einer Eigenschaft der Verbindung (ausführbare Datei, Port, IP usw.) filtern.&lt;/p&gt;&lt;p&gt;Mit diesen Optionen können Sie mehrere Felder auswählen, nach denen Verbindungen gefiltert werden sollen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Verbindungen auch filtern nach:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Wenn aktiviert, wird dieses Feld ausgewählt, wenn ein Popup angezeigt wird</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>User ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Ziel Port</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Ziel-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dieses Timeout ist der Countdown, den Sie sehen, wenn ein Popup-Dialogfeld angezeigt wird.&lt;/p&gt;&lt;p&gt;Wenn das Popup nicht beantwortet wird, werden die Standardoptionen angewendet.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Datenbanktyp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Auswählen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Popup-Standardaktion.&lt;/p&gt;&lt;p&gt;Wenn eine neue ausgehende Verbindung hergestellt werden soll, wird diese Aktion standardmäßig ausgewählt. Wenn das Timeout auftritt, wird diese Option angewendet.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Während ein Pop-up den Benutzer auffordert, eine Verbindung zuzulassen oder abzulehnen:&lt;/p&gt;&lt;p&gt;1. neue ausgehende Verbindungen werden verweigert.&lt;/p&gt;&lt;p&gt;2. bekannte Verbindungen werden nach den vom Benutzer definierten Regeln zugelassen oder verweigert.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Standardaktion, wenn die GUI getrennt ist</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Debugge ungültige Verbindungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Pop-ups</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Standardoptionen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Standardposition auf dem Bildschirm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>jede temporäre Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Wenn diese Option ausgewählt ist, werden die Regeln der ausgewählten Dauer nicht zur Liste der temporären Regeln in der GUI hinzugefügt.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporäre Regeln sind weiterhin gültig und Sie können sie verwenden, wenn Sie dazu aufgefordert werden, eine neue Verbindung zuzulassen/zu verweigern.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Speichern Sie keine Regeln der Dauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Zeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Ziel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Prozess</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Wenn diese Option aktiviert ist, fordert Opensnitch Sie aus verschiedenen Gründen auf, Verbindungen zuzulassen oder zu verweigern, die keine zugeordnete PID haben, hauptsächlich aufgrund von Verbindungen mit schlechtem Status.&lt;/p&gt;&lt;p&gt;Der Popup-Dialog enthält nur Informationen über die Netzwerkverbindung.&lt;/p&gt;&lt;p&gt;Es gibt jedoch einige Szenarien, in denen dies gültige Verbindungen sind, z. B. beim Einrichten eines VPN mit Wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Desktop-Benachrichtigungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Systembenachrichtigungen verwenden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Qt-Benachrichtigungen verwenden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>Minuten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>Tage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>Ablehnen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Design</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished">Regeln</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Prozessdetails</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>wird geladen...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: Laden...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>Speicherstatistik: Laden ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Dateien öffnen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O Statistiken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Dateien in den Speicher geladen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Stapel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Umgebungsvariablen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Anwendungs-PIDs</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Starten oder beenden Sie die Überwachung dieses Prozesses</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Schließen</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Regel auf alle Knoten anwenden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Von dieser Kommandozeile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Von dieser ausführbaren Datei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Aktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/Pfad/zur/ausführbaren/Datei, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>Zu dieser IP / Netzwerk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>einmal</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/>
+        <source>30s</source>
+        <translation type="obsolete">30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/>
+        <source>5m</source>
+        <translation type="obsolete">5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/>
+        <source>15m</source>
+        <translation type="obsolete">15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/>
+        <source>30m</source>
+        <translation type="obsolete">30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/>
+        <source>1h</source>
+        <translation type="obsolete">1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>immer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>Zu diesem Port</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Von dieser Benutzer-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Kommas oder Leerzeichen dürfen nicht mehrere Domänen angeben.
+
+Verwenden Sie stattdessen reguläre Ausdrücke:
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+oder eine einzelne Domain:
+www.gnu.org - es wird nur mit www.gnu.org, noch ftp.gnu.org oder www2.gnu.org übereinstimmen, ...
+gnu.org - es wird nur mit gnu.org, www.gnu.org oder ftp.gnu.org übereinstimmen, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domain.org, .*\.domain.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Es sind nur TCP-, UDP- oder UDPLITE-Optionen zulässig.&lt;/p&gt;&lt;p&gt;Sie können reguläre Ausdrücke verwenden zu diesen Optionen, zum Beispiel TCP oder UDP: ^ (TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Sie können eine IP angeben:
+- 192.168.1.1
+
+oder ein regulärer Ausdruck:
+- 192\.168\.1\.[0-9]+
+
+mehrere IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Sie können auch ein Subnetz angeben:
+- 192.168.1.0/24
+
+Hinweis: Kommas und Leerzeichen dürfen keine IPs oder Netzwerke angeben.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/>
+        <source>LAN</source>
+        <translation type="obsolete">LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Dauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>Zu diesem Host</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Verweigern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Erlauben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Name</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Aktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Regeln werden in alphabetischer Reihenfolge überprüft, daher können Sie diese so benennen, um sie zu priorisieren.
+
+000-allow-localhost
+0001-Deny-Broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">Lassen Sie das Feld leer, um den Namen automatisch zuzuweisen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Wenn Sie diese Option aktivieren, hat diese Regel bei der Bewertung Vorrang vor den übrigen Regeln. Danach werden keine Regeln mehr überprüft.
+
+Sie müssen die Regel so benennen, dass sie zuerst überprüft wird, da sie in alphabetischer Reihenfolge überprüft wird. Zum Beispiel:
+
+[x] Priorität - 000-Prioritätsregel
+[] Priorität - 001-Regel mit weniger Priorität</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Prioritätsregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Standardmäßig wird bei den Feldern einer Regel NICHT zwischen Groß- und Kleinschreibung unterschieden, d. H.; Wenn ein Prozess versucht, auf gOOgle.CoM zuzugreifen, und Sie eine Regel zum Verweigern haben. * Google.com, wird die Verbindung blockiert.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Wenn Sie diese Option aktivieren und gOOgle.CoM GENAU blockieren möchten, müssen Sie dies im Regelfeld angeben, also die genaue Domain, die Sie filtern möchten (in diesem Fall: gOOgle.CoM).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Groß- und Kleinschreibung beachten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sie können mehrere Ports mit regulären Ausdrücken angeben:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 oder 443:
+&lt;/p&gt;&lt;p&gt;^ (53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 oder 5551, 5552, 5553 usw.:&lt;/p&gt;&lt;p&gt;^ (53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>Bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Zu dieser Domainliste</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Wählen Sie ein Verzeichnis mit Domänenlisten aus, die blockiert oder zugelassen werden sollen.&lt;/p&gt;&lt;p&gt;Legen Sie in diesem Verzeichnis Dateien mit einer beliebigen Erweiterung ab, die Listen von Domänen enthalten.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Das Format jedes Eintrags einer Liste ist wie folgt (Hosts-Format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Anwendungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Netzwerk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch-Netzwerkstatistik</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Als CSV exportieren.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Strg+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Erstellen Sie eine neue Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Abfangen starten oder stoppen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Ereignisse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Erlauben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Verweigern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Beispiel: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(Doppelklicken Sie auf die Addressenspalte, um Details eines Knotens anzuzeigen)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Regeln</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>aktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="671"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(Doppelklicken Sie auf die Namenspalte, um Details einer Regel anzuzeigen.)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">Suchregelname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Anwendungsregeln</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Dauerhaft</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Temporär</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(Doppelklicken Sie auf eine Element, um Details anzuzeigen.)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Anwendungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adressen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Ports</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Benutzer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Verbindungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Abgelehnt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Betriebszeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Löschen Sie alle abgefangenen Ereignisse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Regel bearbeiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Regel löschen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Löschen Sie alle abgefangenen Hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Löschen Sie alle abgefangenen Anwendungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Löschen Sie alle abgefangenen Adressen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Löschen Sie alle abgefangenen Ports</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Löschen Sie alle abgefangenen Benutzer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(Doppelklicken Sie auf eine Zeile, um Details zu einer Regel anzuzeigen)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Verbindungen löschen, die dieser Regel entsprechen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Alle Anwendungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statistiken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Hilfe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Schließen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Aktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Deaktivieren</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished">Konfiguration angewendet.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished">Quell-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished">Warnung</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Erlauben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Verweigern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>für immer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Ausgehende Verbindung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Prozess ausgeführt von:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>von dieser Kommandozeile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>von dieser ausführbaren Datei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Unbekannter Prozess</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>Bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>zum Port {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; stellt eine Verbindung zu &lt;b&gt;%s&lt;/b&gt; an Port%s %d her</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;Remote-Prozess &lt;b&gt;%s&lt;/b&gt;, der auf &lt;b&gt;%s&lt;/b&gt; ausgeführt wird, stellt eine Verbindung zu &lt;b&gt;%s&lt;/b&gt; auf %s Port %d her</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>zu {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>UID {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>zu {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>zu *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">zu *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Remote-Prozess &lt;/b&gt; %s wird ausgeführt auf &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>stellt eine Verbindung zu &lt;b&gt;%s&lt;/b&gt; auf %s Port %d her</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>versucht &lt;b&gt;%s&lt;/b&gt; über%s,%s Port%d aufzulösen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Fehler beim Speichern der Konfiguration: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Konfiguration in %s anwenden ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Die Serveradresse darf nicht leer sein</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Fehler beim Laden der Konfiguration %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Konfiguration angewendet.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Fehler beim Anwenden der Konfiguration: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Fehler beim Speichern der Konfiguration: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Konfiguration in {0} anwenden ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Fehler beim Laden der Konfiguration {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Fehler beim Anwenden der Konfiguration: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Warnung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Sie müssen eine Datei für die Datenbank auswählen&lt;br&gt;oder wählen Sie den Typ &quot;Im Speicher&quot;.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>DB-Typ geändert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Starten Sie die GUI neu, damit die Effekte wirksam werden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Fahren Sie mit der Maus über die Texte, um die Hilfe anzuzeigen&lt;br&gt;&lt;br&gt;Vergessen Sie nicht, das Wiki zu besuchen: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Fehler beim Laden der Prozessinformationen:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Fehler beim Beenden des Überwachungsprozesses:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>Wird geladen...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Es sind keine Knoten verbunden.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Regel angewendet.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Fehler beim Anwenden der Regel:%s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>Das Protokoll darf nicht leer sein oder die Option deaktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Protokoll-Regexp-Fehler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>Prozesspfad darf nicht leer sein</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Prozesspfad-Regexp-Fehler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>Befehlszeile darf nicht leer sein oder die Option deaktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Befehlszeilen-Regexp-Fehler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Der Zielport darf nicht leer sein oder die Option deaktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Fehler im regulären Ausdruck des Zielports</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Der Zielhost kann nicht leer sein oder die Option deaktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Fehler beim regulären Ausdruck des Zielhosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Ziel-IP / Netzwerk darf nicht leer sein</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Fehler beim regulären Ausdruck der Ziel-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Die Benutzer-ID darf nicht leer sein oder die Option deaktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Regexp-Fehler der Benutzer-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Fehler beim Anwenden der Regel: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Fehler beim Laden der Regel&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Listenfeld darf nicht leer sein</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Listenfeld muss ein Verzeichnis sein</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Regel nicht unterstützt&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Gestoppt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Deaktiviert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Eingeschaltet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Sie sind im Begriff, diese Regel zu löschen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Bist du sicher?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch-Netzwerkstatistiken {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch-Netzwerkstatistiken für {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Name</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Regeln</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Zeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Aktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Dauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation type="unfinished">Treffer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Als CSV speichern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Aktiviert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Löschen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="575"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Fehler:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Warnung:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Erlauben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Verweigern</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Immer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Bis zum Neustart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Deaktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Aktivieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Duplizieren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Bearbeiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Regel von diesem Namen und Knoten nicht gefunden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Sie sind dabei, diese Regel zu löschen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Name</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Name</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regeln</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Zeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aktiviert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Treffer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Name</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regeln</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Zeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Aktion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Dauer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Knoten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Aktiviert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Treffer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Prozess</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ziel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>BenutzerID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>LetzteVerbindung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>ZielIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>ZielHost</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>ZielPort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="174"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">LetzteVerbindung</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="179"/>
+        <source>Uptime</source>
+        <translation type="obsolete">Betriebszeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Verbindungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Abgelehnt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Betriebszeit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Verbindungen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Abgelehnt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/es_ES/opensnitch-es_ES.ts b/ui/i18n/locales/es_ES/opensnitch-es_ES.ts
new file mode 100644 (file)
index 0000000..8572881
--- /dev/null
@@ -0,0 +1,3301 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="es_ES">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>UID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>Ejecutado desde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>Etiqueta de texto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>IP origen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>IP destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Puerto destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>de este ejecutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>de este comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>este puerto destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>este usuario</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>esta IP destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>una vez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>para siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation type="unfinished">Denegar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>Hasta reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>a partir de este PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30 segundos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5 minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15 minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30 minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1 hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished">Cerrar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished">Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished">Descripción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished">Eliminar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished">Guardar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Preferencias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>UI</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Timeout por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Duración por defecto (de la acción/regla)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Duración por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Filtrado por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>centro</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>Arriba a la derecha</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>Abajo a la derecha</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>Arriba a la izquierda</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>Abajo a la izquierda</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>por ejecutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>por comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>por puerto destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>por IP destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>por UID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>una vez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>para siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>Denegar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Deshabilitar ventanas emergentes,
+sólo mostrar alerta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Nodos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Método parar monitorizar procesos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>La Duración por defecto se aplicará cuando no haya ninguna UI conectada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dirección del nodo.&lt;/p&gt;&lt;p&gt;Por defecto: unix:///tmp/osui.sock (unix:// es obligatorio si es un socket Unix)&lt;/p&gt;&lt;p&gt;También puede ser una IP con este formato: 127.0.0.1:50051, 192.168.1.122:12345, etc..&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Nivel de log por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>La Acción por defecto se aplicará cuando no haya ninguna UI conectada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Fichero en el que escribir los logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout escribirá los logs por la salida estándar del servicio.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Fichero de log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Nombre del host</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Aplicar configuración a todos
+los nodos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Datos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>En memoria</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Fichero</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Cerrar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Aplicar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Guardar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>Hasta reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Tipo de base de datos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Seleccionar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Mostrar vista avanzada por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>Si se selecciona, las ventanas emergentes se mostrarán con la vista avanzada activada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>Por defecto cuando una ventana emergente aparece, en su forma más simple, puedes filtrar conexiones por un
+parámetro de la conexión (ejecutable, puerto, IP, etc).
+
+Con estas opciones, puedes seleccionar varios campos por los que filtrar por defecto conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Filtrar conexiones también por:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>UID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Puerto destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>IP destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>Este timeout es la cuenta atrás que ves cuando se muestra una ventana emergente
+
+Si no respondes a la ventana emergente, se aplicarán las opciones por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>La vista avanzada te permite seleccionar fácilmente múltiples campos para filtrar conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Si se selecciona, este campo estará marcado cuando una ventana emergente aparezca</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Acción por defecto de la ventana emergente.&lt;/p&gt;&lt;p&gt;Cuando una nueva conexión saliente está a punto de establecerse, esta acción será la predeterminada, por lo que si llega el timeout, está será la que se aplique.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Mientras una ventana emergente está activa esperando ser aprobada o denegada:&lt;/p&gt;&lt;p&gt;1. Las nuevas conexiones salientes son denegadas (según la configuración del demonio)&lt;/p&gt;&lt;p&gt;2. Las conexiones ya conocidas se permitirán o denegarán en base a las reglas ya creadas por el usuario.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Opción por defecto cuando la GUI no está conectada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Depurar conexiones inválidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Opciones por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Posición por defecto en la pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>cualquier regla temporal</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Cuando esta opción está seleccionada, las reglas de la duración elegida no se añadirán a la lista de reglas temporales en la GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Las reglas temporales seguirán siendo válidas, y puedes usarlas cuando se pregunte para permitir o denegar una nueva conexión.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">No guardar reglas de duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Si se selecciona opensnitch te preguntará para permitir o denegar conexiones que no tienen un PID asociado. Esto puede pasar por diferentes motivos, principalmente debido a conexiones inválidas.&lt;/p&gt;&lt;p&gt;La ventana emergente sólo contendrá información de la conexión.&lt;/p&gt;&lt;p&gt;Hay algunas situaciones en las que estas conexiones son válidas, por ejemplo al establecer un túnel VPN con wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>por PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Deshabilitar ventanas emergentes, sólo mostrar notificaciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Notificaciones de escritorio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Usar notificaciones del sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Usar notificaciones de Qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation>Probar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Si lo marcas, OpenSnitch sólo te preguntará para denegar o permitir conexiones que por diversas razones no tengan un PID/aplicación asociado. Generalmente son conexiones en estado erróneo.&lt;/p&gt;&lt;p&gt;La ventana emergente sólo contendrá información sobre la conexión de red.&lt;/p&gt;&lt;p&gt;Hay algunos casos en los que estas conexiones pueden ser válidas, como cuando se establecen conexiones VPN.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation>Minutos entre borrado de eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>días</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Máximo de días a guardar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>rechazar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation>Sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation>Línea de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Tema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30 segundos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5 minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15 minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30 minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1 hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Detalles del proceso</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>cargando...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: cargando...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>estadísticas de memoria: cargando...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Ficheros abiertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>Estadísticas Entrada/Salida</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Ficheros cargados en memoria</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Pila</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Variables de entorno</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>PIDs de la aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Iniciar o Parar el monitorizado de este proceso</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Aplicar regla a todos los nodos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>De este comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>De este ejecutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/ruta/al/ejecutable, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>A esta IP/Red</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>una vez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>A este puerto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>De este UID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>No se permiten ni comas ni espacios para especificar múltiples dominios.
+
+Puedes usar expresiones regulares en su lugar:
+
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+o un único dominio:
+www.gnu.org - sólo filtrará www.gnu.org, NO filtrará ftp.gnu.org ni www2.gnu.org
+gnu.org         - sólo filtrará gnu.org, ni www.gnu.org, ni ftp. gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.dominio.org, .*\.dominio.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>Sólo se permiten las opciones TCP, UDP o UDPLITE. Puedes usar expresiones regulares
+sobre estas opciones, por ejemplo TCP o UDP: ^(TCP|UDP)$</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Puedes especificar una IP:
+- 192.168.1.1
+
+o una expresión regular:
+- 192\.168\.1\.[0-9]+
+
+múltiples IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+También puedes especificar una subnet:
+- 192.168.1.0/24
+
+Nota: No se permiten ni comas ni espacios para especificar IPs o redes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>A este host</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Denegar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Las reglas se comprueban en orden alfabético, por lo que debes nombrarlas así para priorizarlas.
+
+000-allow-localhost
+0001-deny-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">dejar en blanco para autoasignar nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Si marcas esta opción, esta regla tendrá prioridad sobre el resto de reglas cuando le toque evaluarla. No se comprobará ninguna regla más después de esta si coincide.
+
+Debes nombrar la regla de tal manera que se compruebe de las primeras, ya que se nombran alfabéticamente. Por ejemplo:
+
+[x] Prioritaria-000-regla-prioritaria
+[  ] Prioritaria-001-regla-menos-prioritaria</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Prioritaria</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Por defecto los campos de una regla NO son sensibles a mayúsculas/minúsculas, es decir, si un proceso trata de acceder a gOOgle.CoM y tienes una regla para Denegar .*google.com, la conexión será bloqueada. &lt;br/&gt;&lt;/p&gt;&lt;p&gt;Si marcas esta opción y quieres bloquear EXACTAMENTE gOOgle.CoM, tendrás que especificar en el campo de la regla el dominio exacto que quieres filtrar (en este caso: gOOgle.CoM).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Distinguir mayúsculas/minúsculas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Puedes especificar múltiples puertos usando expresiones regulares:
+
+- 53, 80 o 443:
+^(53|80|443)$
+
+- 53, 443 o 5551, 5552, 5553, etc:
+^(53|443|555[0-9])$</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>Hasta reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>A esta lista de dominios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con listas de dominios para permitir o denegar.&lt;/p&gt;&lt;p&gt;Mete dentro de este directorio ficheros con cualquier extensión que contengan listas de dominios.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;El formato de cada dominio de la lista tiene que estar en formato hosts, así:&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;o &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Aplicaciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will only match the executable path. It is not modifiable by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;You can use regular expressions to deny executions from /tmp for example:&lt;br/&gt;&lt;/p&gt;&lt;p&gt;^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este campo sólo comprueba la ruta del ejecutable (la cual no es modificable por el usuario).&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Puedes usar expresiones regulares para denegar cualquier ejecución desde /tmp, por ejemplo; ^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este campo contendrá y comprobará solamente la linea de comandos tecleada por el usuario.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Si el usuario sólo escribió el comando (sin la ruta absoluta), sólo aparecerá el comando:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Si el usuario escribió la ruta absoluta o relativa al comando, eso es lo que aparecerá:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>De este PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Red</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation>A esta lista de dominios/IPs</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation>A esta lista de rangos de red</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation>A esta lista de IPs</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con ficheros que contengan listas de IPs a bloquear o permitir:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;Una IP por linea. Las lineas en blanco o que comiencen con # serán ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con ficheros que contengan listas de rangos de red a bloquear o permitir:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Un rango de red por linea. Las lineas en blanco o que comiencen con # serán ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con ficheros que contengan listas de dominios a bloquear o permitir.&lt;/p&gt;&lt;p&gt;Los ficheros de ese directorio pueden tener cualquier extensión.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;El formato de cada linea es como sigue (formato hosts):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Las lineas en blanco o que comiencen con # serán ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>A esta lista de dominios
+(expresiones regulares)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con ficheros que contengan expresiones regulares de dominios para bloquear o permitir:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;También puedes usar un dominio literal (sin expresión regular): &amp;quot;example.com&amp;quot; ,comprobará whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;Un dominio por linea. Las lineas en blanco o que comiencen con # serán ignoradas&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Rechazar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation>Descripción...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;El valor de este campo es siempre la ruta absoluta al ejecutable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Ejemplos:&lt;/p&gt;&lt;p&gt;- Sencillo: /path/to/binary&lt;/p&gt;&lt;p&gt;- Múltiples caminos: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Binarios múltiples: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Denegar/permitir ejecuciones de /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Para ver más ejemplos, visite &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;página en la wiki&lt;/a&gt; o pregunte en los &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Foros de debate&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>Es una expresión regular</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation>es una expresión regular</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation>Interfaz de la red</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation>Más</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>No registrar conexiones que coincidan con esta regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation>No registrar conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Denegar sólo descartará la conexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Si se rechaza, se cancelará la conexión y se eliminará el socket que la inició</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>Permitir autorizará la conexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>Eventos de red - OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Exportar a CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Crear una nueva regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;Nombre del host - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Parar o iniciar la interceptación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filtrar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Denegar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Ejemplo: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Nodos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Dirección para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">buscar regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Reglas de aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Permanentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Temporales</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Dominios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en un dominio para ver detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Aplicaciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Direcciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Puertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Usuarios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Rechazadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Tiempo de ejecución</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Borrar todos los eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Editar regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Borrar regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Borrar todos los hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Borrar todos las aplicaciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Borrar todas las direcciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Borrar todos los puertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Borrar todos los usuarios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(Doble click en una fila para editar una regla)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Borrar conexiones que coinciden con esta regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Todas las reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Rechazar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation>Normas del sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Ayuda</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Cerrar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Deshabilitar</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished">Configuración aplicada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished">IP origen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation>Información</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation>Fallo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation>Advertencia</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>Las notificaciones de sistema no están disponibles, tienes que instalar python3-notify2.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Denegar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>para siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Conexión saliente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Proceso ejecutado desde:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>este comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>este ejecutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>Hasta reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>puerto {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>a {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>UID {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>a {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>a *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">a *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>El proceso &lt;b&gt;Remoto&lt;/b&gt; %s ejecutado en &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>está tratando de resolver &lt;b&gt;%s&lt;/b&gt; via %s, %s puerto %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>de este PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation>Nueva conexión saliente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Rechazar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>La dirección del servidor no puede estar vacía</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Configuración aplicada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Error al guardar la configuración: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Aplicando configuración en {0} ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Error al cargar la configuración {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Error al aplicar la configuración: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Aviso</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Debes seleccionar un fichero para la base de datos&lt;br&gt;o elegir el tipo En memoria.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>El tipo de BBDD ha cambiado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Reinicia la GUI para que los cambios surtan efecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Pasa el ratón sobre los textos para mostrar la ayuda&lt;br&gt;&lt;br&gt;Y no olvides visitar el wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation>Sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>Temas no disponibles. Instalar qt-material: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation>Cambio del tema de la interfaz de usuario</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Reinicie la interfaz gráfica de usuario para aplicar el nuevo tema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation>De acuerdo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="472"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation type="obsolete">Reinicie la interfaz gráfica de usuario para que los cambios surtan efecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Error al carga la información del proceso:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Error al parar de monitorizar el proceso:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>cargando...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>No hay nodos conectados.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Regla aplicada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>el protocolo no puede estar vacío, o desmarca la opción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Error en la expresión regular del Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>La ruta del ejecutable no puede estar vacía</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Error en la expresión regular del ejecutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>El comando no puede estar vacío, o desmarca la opción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Error en la expresión regular del Comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>El puerto destino no puede estar vacío, o desmarca la opción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Error en la expresión regular del Puerto Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>El Host destino no puede estar vacío, o desmarca la opción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Error en la expresión regular del Host de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>La IP/Red de destino no puede estar vacía</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Error en la expresión regular de IP/Red de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>El ID de Usuario no puede estar vacío, o desmarca la opción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Error en la expresión regular del ID de Usuario</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Error al aplicar la regla: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>El campo Listas de dominios no puede estar vacío</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>El campo Listas debe ser un directorio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Tipo de regla no soportada&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Error cargando la regla&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Ya hay una regla con este nombre.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>El campo de PID no puede estar vacío</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>Error en la expresión regular del PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>Selecciona al menos un campo.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation>La interfaz de red no puede estar vacía</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation>Error de la expresión regular de la interfaz de la red</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Parado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Deshabilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Interceptando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Estás a punto de borrar esta regla.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    ¿Estás seguro?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>Eventos de red OpenSnitch {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>Eventos de red OpenSnitch de {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Guardar como CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Eliminar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Deshabilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Duplicar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Editar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Regla no encontrada por ese nombre o nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Aviso:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Denegar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Hasta reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Estás a punto de borrar esta regla.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="174"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Args</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>IPDestino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>HostDestino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>PuertoDestino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="175"/>
+        <source>Addr</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Rechazadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Qué</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Aplicar a</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Rechazar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Red</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="378"/>
+        <source>Addr</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tiempo de ejecución</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Rechazadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Qué</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Prioritaria</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Nuevo nodo conectado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Descripción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Línea en cmd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts b/ui/i18n/locales/eu_ES/opensnitch-eu_ES.ts
new file mode 100644 (file)
index 0000000..36a572e
--- /dev/null
@@ -0,0 +1,2695 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="eu_ES">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/fi_FI/opensnitch-fi_FI.ts b/ui/i18n/locales/fi_FI/opensnitch-fi_FI.ts
new file mode 100644 (file)
index 0000000..8361735
--- /dev/null
@@ -0,0 +1,2874 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fi_FI" sourcelanguage="en">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>Käyttäjä-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Käynnistetty kohteesta&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Lähde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>Prosessi-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Kohde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Kohdeportti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>tästä ohjelmatiedostosta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>tästä komentorivistä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>tästä kohdeportista</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>tältä käyttäjältä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>tästä kohde-IP:stä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>tästä PID:stä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>kerran</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1t</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>uudelleenkäynnistykseen asti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>ikuisesti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>toiminto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Salli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Palomuuri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Palomuuri&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Profiili</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Estä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>Lähtevä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>Tuleva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>Salli tulevat yhteydet porttiin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>Salli palvelu (IN)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>Poissulje porttiin lähtevät yhteydet sieppaukselta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation>Salli palvelu (OUT)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation>Uusi sääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="421"/>
+        <source>Close</source>
+        <translation>Sulje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Palomuurisääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Solmu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>Ota käyttöön</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>Kuvaus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>Yksinkertainen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>Lisää uusi ehto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>Poista valittu ehto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="221"/>
+        <source>Direction</source>
+        <translation>Suunta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="232"/>
+        <source>IN</source>
+        <translation>IN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="241"/>
+        <source>OUT</source>
+        <translation>OUT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="250"/>
+        <source>FORWARD</source>
+        <translation>FORWARD</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="255"/>
+        <source>PREROUTING</source>
+        <translation>PREROUTING</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="260"/>
+        <source>POSTROUTING</source>
+        <translation>POSTROUTING</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="268"/>
+        <source>Action</source>
+        <translation>Toiminto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="279"/>
+        <source>ACCEPT</source>
+        <translation>ACCEPT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="288"/>
+        <source>DROP</source>
+        <translation>DROP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="297"/>
+        <source>REJECT</source>
+        <translation>REJECT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="306"/>
+        <source>RETURN</source>
+        <translation>RETURN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="315"/>
+        <source>QUEUE</source>
+        <translation>QUEUE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="323"/>
+        <source>DNAT</source>
+        <translation>DNAT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="328"/>
+        <source>SNAT</source>
+        <translation>SNAT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="333"/>
+        <source>REDIRECT</source>
+        <translation>REDIRECT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="349"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation>toiminnosta (eli kohteesta) riippuen parametrien syntaksi vaihtelee.
+Joitakin esimerkkejä:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="432"/>
+        <source>Clear</source>
+        <translation>Tyhjennä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="443"/>
+        <source>Delete</source>
+        <translation>Poista</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="454"/>
+        <source>Save</source>
+        <translation>Tallenna</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="465"/>
+        <source>Add</source>
+        <translation>Lisää</translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Asetukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Ponnahdusikkunat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Oletusasetukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Jos tämä kenttä on valittuna, se valitaan, kun ponnahdusikkuna tulee näkyviin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>Käyttäjä-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Kohdeportti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Kohde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>estä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>salli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>hylkää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ponnahdusikkunan oletustoiminto.&lt;/p&gt;&lt;p&gt;Kun uutta lähtevää yhteyttä ollaan muodostamassa, tämä toiminto valitaan oletusarvoisesti.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Kun ponnahdusikkunassa kysytään käyttäjältä yhteyden sallimista tai kieltämistä:&lt;/p&gt;&lt;p&gt;1. Uudet lähtevät yhteydet kielletään.&lt;/p&gt;&lt;p&gt;2. Tunnetut yhteydet sallitaan tai kielletään käyttäjän määrittelemien sääntöjen perusteella.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Toiminto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>keskellä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>ylhäällä, oikealla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>alhaalla, oikealla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>ylhäällä, vasemmalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>alhaalla, vasemmalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>kerran</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1t</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>uudelleenkäynnistykseen asti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>ikuisesti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Oletusarvoisesti kun uusi ponnahdusikkuna tulee näkyviin, yksinkertaisimmillaan voit suodattaa yhteyksiä tai sovelluksia yhden yhteyden ominaisuuden perusteella (ohjelmatiedosto, portti, IP-osoite jne.).&lt;/p&gt;&lt;p&gt;Vaihtoehtojen avulla voit valita useita kenttiä, joiden perusteella suodatat yhteyksiä.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Suodata yhteydet myös seuraavilla tavoilla:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>ohjelmatiedostoston mukaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>komentorivin mukaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>kohdeportin mukaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>kohde-IP:n mukaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>käyttäjä-ID:n mukaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>PID:n mukaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Oletuskohde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Oletussijainti näytössä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Ponnahdusikkunan oletuskesto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Kesto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>Edistyneessä näkymässä voit helposti valita useita kenttiä suodatettavia yhteyksiä varten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Näytä laajennettu näkymä oletusarvoisesti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Jos tämä on valittuna, ponnahdusikkunat näytetään, kun laajennettu näkymä on aktiivinen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tämä aikakatkaisu on lähtölaskenta, joka näkyy, kun ponnahdusikkuna näytetään.&lt;/p&gt;&lt;p&gt;Jos ponnahdusikkunaan ei vastata, käytetään oletusasetuksia.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Oletusaikakatkaisu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Poista ponnahdusikkunat käytöstä ja näytä vain ilmoitus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Käyttöliittymä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Työpöytäilmoitukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Käytä järjestelmän ilmoituksia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Käytä Qt-ilmoituksia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation>Testaa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Tapahtumavälilehden sarakkeet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Aika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Sääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Solmu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protokolla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Kohde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Prosessi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation>Komentorivi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Teema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation>Järjestelmä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation>Kieli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation>Säännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation>Kun tämä vaihtoehto on valittuna, valitun keston sääntöjä ei lisätä käyttöliittymän väliaikaisten sääntöjen luetteloon.
+
+Väliaikaiset säännöt ovat edelleen voimassa, ja voit käyttää niitä, kun sinua pyydetään sallimaan/kieltämään uusi yhteys.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>Älä tallenna/poista sääntöjä kestolta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>miltään väliaikaisilta säännöiltä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation>30s tai vähemmältä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation>5m tai vähemmältä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation>15m tai vähemmältä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation>30m tai vähemmältä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation>1t tai vähemmältä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Solmut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Prosessin monitorointimekanismi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Lokitiedosto lokien kirjoittamista varten.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout tulostaa lokit vakiolähdölle.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Logitiedosto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Oletuskesto otetaan käyttöön, kun käyttöliittymää ei ole kytketty.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Oletuskesto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Sovella asetuksia kaikkiin solmuihin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Vakiotoiminto suoritetaan, kun käyttöliittymää ei ole yhdistetty.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Oletustoiminto, kun käyttöliittymän yhteys on katkaistu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Isäntänimi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>uudelleenkäynnistykseen asti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>aina</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Solmun osoite.&lt;/p&gt;&lt;p&gt;Esimerkintä: unix:///tmp/osui.sock (unix:// on pakollinen, jos kyseessä on Unix-soketti)&lt;/p&gt;&lt;p&gt;Se voi olla myös IP-osoite portin kanssa: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Osoite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Jos valittuna, OpenSnitch pyytää sinua sallimaan tai kieltämään yhteydet, joihin ei ole liitetty PID:iä, useista syistä, useimmiten huonojen yksien takia.&lt;/p&gt;&lt;p&gt;Ponnahdusikkuna sisältää vain tietoja verkkoyhteydestä.&lt;/p&gt;&lt;p&gt;Jossain tilanteissa nämä yhteydet ovat kuitenkin kelvollisia yhteyksiä, kuten esimerkiksi luodessasi VPN:ää WireGuardin avulla.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Vianmääritä virheellisiä yhteyksiä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Versio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Oletuslogitaso</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Tietokanta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>Muistissa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Tiedostossa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Tietokantatyyppi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Valitse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>minuuttia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation>Tapahtumien puhdistusväli minuuteissa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>päivää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Tapahtumien enimmäissäilytys päivissä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Sulje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Toteuta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Tallenna</translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Prosessin tiedot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>ladataan...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: ladataan...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>muistitilastot: ladataan...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Tila</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Avoimet tiedostot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O-tilastot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Muistikartoitetut tiedostot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Pino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Ympäristömuuttujat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Sovelluksen PID:it</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Aloita tai pysäytä tämän prosessin monitorointi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>SUlje</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Sääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Toiminto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Kesto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>kerran</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>uudelleenkäynnistykseen asti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>aina</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Esto vain sivuuttaa yhteyden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Estä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Hylkäys pudottaa yhteyden ja tappaa sen aloittaneen liitännän</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Hylkää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>Salli sallii yhteyden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Salli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Ota käyttöön</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Jos valintaruutu on valittuna, tämä sääntö on etusijalla muihin sääntöihin nähden. Muita sääntöjä ei tarkisteta tämän säännön jälkeen.
+
+Sinun on nimettävä sääntö siten, että se tarkistetaan ensimmäisenä, koska säännöt tarkistetaan aakkosjärjestyksessä. Esimerkiksi:
+
+[x] Prioriteetti - 000-prioriteettisääntö
+[ ] Prioriteetti - 001-alhaisempi prioriteettisääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Prioriteettisääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Säännöt tarkistetaan aakkosjärjestyksessä, joten voit nimetä ne sen mukaan ja asettaa ne tärkeysjärjestykseen.
+
+000-allow-localhost
+001-deny-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation>Nimi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Solmu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Sovella sääntöä kaikkiin solmuihin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Sovellukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tämän kentän arvo on aina suoritettavan tiedoston absoluuttinen polku: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt; Esimerkkejä:&lt;/p&gt;&lt;p&gt;- Simple: /&lt;/p&gt;&lt;p&gt;- Useita polkuja: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Useita binäärejä: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Kielletään/sallitaan suoritukset /tmp:stä:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt; Lisää esimerkkejä löydät &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki-sivulta&lt;/a&gt; tai kysy &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;keskustelufoorumeilla&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>Onko säännöllinen lauseke</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Tältä käyttäjä-ID:ltä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Tästä komentorivistä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Tämä kenttä sisältää käyttäjän suorittaman komentorivin ja vastaa sitä.&lt;br/&gt;&lt;/p&gt;&lt;p&gt; Jos käyttäjä kirjoitti komennon, vain komento näkyy:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt; Jos käyttäjä kirjoitti komennon absoluuttisen tai suhteellisen polun, se näkyy:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../../usr/bin/telnet 1.2.3.4.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>Tästä PID:istä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="466"/>
+        <source>From this executable</source>
+        <translation>Tästä ohjelmatiedostosta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="473"/>
+        <source>is regular expression</source>
+        <translation>on säännöllinen lauseke</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="485"/>
+        <source>Network</source>
+        <translation>Verkko</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="520"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Vain TCP, UDP tai UDPLITE ovat sallittuja&lt;/p&gt;&lt;p&gt;Voit käyttää regexp:iä, esim: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="560"/>
+        <source>ICMP</source>
+        <translation>ICMP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="565"/>
+        <source>ICMP6</source>
+        <translation>ICMP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="570"/>
+        <source>SCTP</source>
+        <translation>SCTP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="575"/>
+        <source>SCTP6</source>
+        <translation>SCTP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="586"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Pilkut tai välilyönnit eivät ole sallittuja useiden toimialueiden määrittämisessä. 
+
+Käytä sen sijaan säännöllisiä lausekkeita: 
+.*(opensnitch|duckduckgo).com&quot;.
+.*\.google.com
+
+tai yksittäinen verkkotunnus:
+www.gnu.org - se vastaa vain www.gnu.org, eikä ftp.gnu.org, eikä www2.gnu.org, ...
+gnu.org - vain gnu.org, www.gnu.org, ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domain.org, .*\.domain.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="604"/>
+        <source>To this IP / Network</source>
+        <translation>Tähän IP-osoitteeseen / verkkoon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="627"/>
+        <source>Protocol</source>
+        <translation>Protokolla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="754"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Voit määrittää yhden IP-osoitteen:
+- 192.168.1.1
+
+tai säännöllisen lausekkeen:
+- 192\.168\.1\.[0-9]+
+
+useita IP-osoitteita:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Voit myös määrittää aliverkon:
+- 192.168.1.0/24
+
+Huomautus: Pilkut tai välilyönnit eivät saa erottaa IP-osoitteita tai verkkoja toisistaan.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="659"/>
+        <source>LAN</source>
+        <translation>LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="664"/>
+        <source>MULTICAST</source>
+        <translation>MULTICAST</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="669"/>
+        <source>127.0.0.0/8</source>
+        <translation>127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="674"/>
+        <source>192.168.0.0/24</source>
+        <translation>192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="679"/>
+        <source>192.168.1.0/24</source>
+        <translation>192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="684"/>
+        <source>192.168.2.0/24</source>
+        <translation>192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="689"/>
+        <source>192.168.0.0/16</source>
+        <translation>192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="694"/>
+        <source>169.254.0.0/16</source>
+        <translation>169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="699"/>
+        <source>172.16.0.0/12</source>
+        <translation>172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="704"/>
+        <source>10.0.0.0/8</source>
+        <translation>10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="709"/>
+        <source>::1/128</source>
+        <translation>::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="714"/>
+        <source>fc00::/7</source>
+        <translation>fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="719"/>
+        <source>ff00::/8</source>
+        <translation>ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="724"/>
+        <source>fe80::/10</source>
+        <translation>fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="729"/>
+        <source>fd00::/8</source>
+        <translation>fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="737"/>
+        <source>From this IP / Network</source>
+        <translation>Tästä IP-osoitteesta / verkosta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="744"/>
+        <source>To this host</source>
+        <translation>Tälle isännälle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/>
+        <source>Network interface</source>
+        <translation>Verkkoliitäntä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="866"/>
+        <source>From this port</source>
+        <translation>Tästä portista</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="912"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Voit määrittää useita portteja käyttämällä säännöllisiä lausekkeita:&lt;/p&gt;&lt;p&gt;- 53, 80 tai 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt; &lt;p&gt; - 53, 443 tai 5551, 5552, 5553, jne:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="896"/>
+        <source>To this port</source>
+        <translation>Tähän porttiin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="926"/>
+        <source>List of domains/IPs</source>
+        <translation>Luettelo verkkotunnuksista/IP-osoitteista</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>To this list of network ranges</source>
+        <translation>Tähän verkkoalueiden luetteloon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="939"/>
+        <source>To this list of IPs</source>
+        <translation>Tähän IP-osoitteiden luetteloon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="965"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Valitse hakemisto, jossa on estettävien tai sallittujen IP-osoitteiden luettelon sisältäviä tiedostoja:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;jne.&lt;/p&gt;&lt;p&gt;Yksi IP-osoite per rivi. Tyhjät tai #-alkuiset rivit jätetään huomiotta.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="974"/>
+        <source>To this list of domains</source>
+        <translation>Tähän verkkotunnusten luetteloon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1000"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Valitse hakemisto, jossa on estettävien tai sallittujen verkkoalueiden luettelon sisältäviä tiedostoja:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;jne.&lt;br/&gt;&lt;/p&gt;&lt;p&gt; Yksi verkkoalue per rivi. Tyhjät tai #-alkuiset rivit jätetään huomiotta.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1028"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Valitse hakemisto, jossa on luetteloita estettävistä tai sallittavista verkkotunnuksista.&lt;/p&gt;&lt;p&gt;Laita kyseiseen hakemistoon minkä tahansa tiedostopäätteen omaavia tiedostoja, jotka sisältävät luetteloita verkkotunnuksista.&lt;/p&gt;&lt;p&gt;&lt;br/&gt; Luettelon jokaisen merkinnän muoto on seuraava (hosts-muodossa):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;tai &lt;/p&gt;&lt;p&gt;0.0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Tyhjiä rivejä tai rivejä, jotka alkavat merkinnällä #, ei huomioida.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1043"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>Tähän verkkotunnusten luetteloon 
+(säännölliset lausekkeet)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1070"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Valitse hakemisto, jossa on tiedostoja, jotka sisältävät säännöllisiä lausekkeita estettävistä tai sallittavista verkkotunnuksista:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;Voit myös käyttää verkkotunnusta sellaisenaan: &amp;quot;example.com&amp;quot;, jolloin se vastaa whatever.example.com, whatever.example.com.localdomain jne.&lt;/p&gt;&lt;p&gt;Yksi verkkotunnus riviä kohti. Tyhjät tai #-alkuiset rivit jätetään huomiotta.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1080"/>
+        <source>More</source>
+        <translation>Lisää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Oletusarvoisesti sääntöjen kentässä ei oteta huomioon isoja ja pieniä kirjaimia, eli jos prosessi yrittää käyttää gOOgle.CoM:ää ja sinulla on sääntö Deny .*google.com, yhteys estetään.&lt;br/&gt;&lt;/p&gt;&lt;p&gt; Jos ruksaat tämän ruudun, sinun on määritettävä tarkka merkkijono (verkkotunnus, suoritettava ohjelma, komentorivi), jonka haluat suodattaa.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1089"/>
+        <source>Case-sensitive</source>
+        <translation>Kirjainkoolla on merkitystä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1096"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>Älä logita yhteyksiä, jotka vastaavat tätä sääntöä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1099"/>
+        <source>Don&apos;t log connections</source>
+        <translation>Älä logita yhteyksiä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1145"/>
+        <source>Description...</source>
+        <translation>Kuvaus...</translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch -verkkotilastot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Suodatin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Salli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Estä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Hylkää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Esim.: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Poista kaikki kaapatut tapahtumat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Luo uusi sääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Tila</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Aloita tai lopeta kaappaus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Tapahtumat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Solmut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation>Poista tämä solmu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation>Näytä tämän solmun asetukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation>Tämän solmun kuuntelun aloittaminen tai lopettaminen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Säännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Sovellussäännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Pysyvä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Väliaikainen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation>Järjestelmän säännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Kaikki sovellukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>ota käyttöön</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Muokkaa sääntöä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Poista sääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Isännät</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Sovellukset</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Osoitteet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Portit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Käyttäjät</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Yhteydet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Pudotetut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Käynnissäoloaika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Versio</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Tilastot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Ota käyttöön</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Poista käytöstä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Apua</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Sulje</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation>Asetukset toteutettu.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation>Virhe: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation>Toteutetaan muutoksia...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>Virhe INPUT-ketjun käytännön saamisessa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>Virhe OUTPUT-ketjun käytännön saamisessa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>Jotta voimme määrittää palomuurisääntöjä käyttöliittymästä, meidän on käytettävä &apos;nftables&apos;-ohjelmaa &apos;iptables&apos;-ohjelman sijasta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation>Otetaan käyttöön palomuuria...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation>Otetaan palomuuria pois käytöstä...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Kohdeportti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Lähdeportti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>Kohde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>Lähde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>Tuloliitäntä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>Lähtöliitäntä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>Aseta conntrack-merkki</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>Kohdista conntrack-merkki</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>Kohdista conntrack-tila(t)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>Aseta merkki pakettiin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>Kohdista pakettitiedot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>Kaistanleveyskiintiöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>Nopeusrajoita yhteyksiä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation>
+Tuetut formaatit:
+
+ - 23
+ - Alueet: 80-1024
+ - Useita portteja: 80,443,8080
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation>
+Tuetut formaatit:
+
+ - 1.2.3.4
+ - IP-alueet: 1.2.3.100-1.2.3.200
+ - Verkkoalueet: 1.2.3.4/24
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation>Kohdista tuloliitäntä. Säännölliset lausekkeet eivät ole sallittuja.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation>Sovita lähtöliitäntä. Säännölliset lausekkeet eivät ole sallittuja.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation>Asettaa yhteyden conntrack-merkki desimaalimuodossa.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation>Kohdista yhteyden conntrack-merkki, desimaalimuodossa.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation>Kohdista conntrack-tilat.
+
+Tuetut formaatit:
+ - Yksinkertainen: new
+ - Useita tiloja pilkulla erotettuna: related,new
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation>
+Match-paketin metatiedot.
+
+Arvon on oltava desimaalimuodossa, paitsi &quot;l4proto&quot;-vaihtoehdon tapauksessa.
+l4proto voi olla esimerkiksi pienellä alkukirjaimella kirjoitettu merkkijono:
+ tcp
+ udp
+ icmp,
+ jne
+
+Jos protokollan tai lproton arvo on desimaalinen, se käyttää sitä koodina, joka on
+protokollan koodina.
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation>Asettaa paketille merkki, joka vastaa määritettyjä ehtoja. Arvo on desimaalimuodossa.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation>
+Kohdista ICMP-koodit.
+
+Tuetut muodot:
+ - Yksinkertainen: echo-request
+ - Useita pilkulla erotettuna: echo-request,echo-reply
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation>
+Kohista ICMPv6-koodit.
+
+Tuetut muodot:
+ - Yksinkertainen: echo-request
+ - Useita pilkulla erotettuna: echo-request,echo-reply
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation>Tulostaa viestin, kun tämä sääntö vastaa pakettia.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation>
+Sovelletaan kiintiöitä yhteyksiin.
+
+Esimerkiksi kun:
+ - Sovelletaan määriteltyä toimintoa (DROP), esimerkiksi: &quot;kiintiö yli 10 megatavua&quot;.
+ - &quot;kiintiö enintään 10 megatavua&quot; -&gt; sovelletaan määriteltyä toimintoa (ACCEPT).
+
+Arvon on oltava muotoa: VALUE/UNITS, esimerkiksi:
+ - 10mbytes, 1/gbytes, jne
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation>
+Rajoita yhteyksiä.
+
+Esimerkiksi kun:
+ - Sovelletaan määriteltyä toimintoa (DROP, ACCEPT jne.).
+    (Kun yhteyksiä on yli 10 Mt minuutissa, sovelletaan toimintoa).
+
+ - &quot;rajoitus enintään 10 megatavua/tunti&quot; -&gt; sovelletaan määriteltyä toimintoa (ACCEPT).
+
+Arvon on oltava muotoa: VALUE/UNITS/TIME, esimerkiksi:
+ - 10/mbytes/minute, 1/gbytes/hour, jne
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>Protobuf-versiosi ei ole yhteensopiva, sinun on asennettava protobuf 3.8.0 tai uudempi versio.
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation>Sääntö poistettu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation>Sääntö lisätty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation>Voit käyttää &apos;,&apos; tai &apos;-&apos; -merkkejä määrittääksesi useita portteja/IP-osoitteita tai alueita/arvoja:&lt;br&gt;&lt;br&gt;ports: 22 tai 22,443 tai 50000-60000&lt;br&gt;IP:t: 192.168.1.1 tai 192.168.1.30-192.168.1.130&lt;br&gt;arvot: echo-reply,echo-request&lt;br&gt;arvot: new,established,related</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation>Poistetaan sääntöä, odota</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation>Virhe säännön päivittämisessä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation>Lisäätään sääntöä, odota</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation>&lt;valitse lausuma&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation>num</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation>kohteeseen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation>Yhtä suuri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation>Ei yhtäläinen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation>Suurempi tai yhtä suuri kuin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation>Suurempi kuin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation>Pienempi tai yhtä suuri kuin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation>Vähemmän kuin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation>Palomuurisääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation>Yksinkertainen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation>Edistynyt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation>Tätä sääntöä ei vielä tueta.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation>Sulje palvelu pois</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>Salli saapuvat yhteydet valittuun porttiin.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>Salli lähtevät yhteydet valittuun porttiin.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation>valitse lausuma.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>arvo ei voi olla 0 tai tyhjä.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>arvomuoto on 1024/kbytes (tai bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>arvomuoto on 1024 kbytes/sekunti (tai bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>rajoitus ei kelpaa, käytä: bytes, kbytes, mbytes tai gbytes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>aikaraja ei ole voimassa, käytä: second, minute, hour tai day</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation>portti ei kelpaa.</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation>Tiedot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation>Virheet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation>Varoitukset</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>Järjestelmäilmoitukset eivät ole käytettävissä, sinun on asennettava python3-notify2.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation>Avaa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Salli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Estä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation>Uusi lähtevä yhteys</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>on yhdistämässä &lt;b&gt;%s&lt;/b&gt; kohteen %s portissa %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>uudelleenkäynnistykseen asti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>ikuisesti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Hylkää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Lähtevä yhteys</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Prosessi käynnistetty kohteesta:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>tästä ohjelmatiedostosta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>tästä komentorivistä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>porttiin {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>kohteeseen {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>käyttäjältä {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>tästä PID:istä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>kohteeseen {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>kohteeseen *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Etä&lt;/b&gt;prosessi %s on käynnissä kohteessa &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>yhdistää kohteeseen &lt;b&gt;%s&lt;/b&gt;, %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>yrittää selvittää &lt;b&gt;%s&lt;/b&gt;%s, %s portti %d kautta</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Varoitus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Uudelleenkäynnistä käyttöliittymä uudelleen, jotta muutokset tulevat voimaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>Solmuja ei ole yhdistetty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation>Järjestelmän oletus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation>Järjestelmä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>Teemat eivät ole käytettävissä. Asenna qt-material: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Palvelimen osoite ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Virhe asetuksen {0} lataamisessa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Poikkeus asetusten tallentamisessa: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>Tietokantatyyppi muutettu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Sinun on valittava tiedosto tietokannalle&lt;br&gt;tai valittava tyypiksi &quot;Muistissa&quot;.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation>Kieli muuutettu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation>Käyttöliittymäteema muutettu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Uudelleenkäynnistä käyttöliittymä, jotta uusi teema tulee voimaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Toteutetaan asetuksia {0} ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation>Ok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>Virhe solmun asetusten tallennuksessa {0}: {1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Asetukset toteutettu.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Virhe asetusten toteuttamisessa: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Vie hiiri tekstien päälle näyttääksesi ohjeen.&lt;br&gt;&lt;br&gt;Älä unohda käydä wikissä: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Virhe prosessin tietojen lataamisessa:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Virhe prosessin monitoroinnin pysäyttämisessä:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>ladataan...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Solmuja ei ole yhdistetty.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Tällä nimellä on jo sääntö.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Sovellettu sääntö.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Virhe säännön soveltamisessa: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Virhe säännön lataamisessa&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>protokolla ei voi olla tyhjä, tai poista valintaruutu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Protokollan regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>prosessipolku ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Prosessin polun regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>komentorivi ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Komentorivin regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation>Verkkoliitäntä ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation>Verkkoliitännän regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation>Lähdeportti ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation>Lähdeportin regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Kohdeportti ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Kohdeportin regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Kohdeisäntä ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Kohdeisännän regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation>Lähde-IP/-verkko ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation>Lähde-IP:n regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Kohde-IP/-verkko ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Kohde-IP:n regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Käyttäjä-ID ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Käyttäjä-ID:n regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>PID-kenttä ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>PID-kentän regexp-virhe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Listat-kenttä ei voi olla tyhjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Listat-kentän on oltava hakemisto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>Valitse vähintään yksi kenttä.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Sääntö ei tuettu&lt;/b&gt;</translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation>VAROITUS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Uusi solmu kytketty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Mikä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Osumia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Verkon nimi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Aika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Solmu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Toiminto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kohde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protokolla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Prosessi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Sääntö</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nimi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Osoite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tila</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Isäntänimi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Käynnissäoloaika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Säännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kesto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kuvaus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Käytössä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ensisijaisuus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Osumia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Komentorivi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kohde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kohdeisäntä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kohdeportti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Käyttäjä-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Viimeinen yhteys</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Ei käynnissä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Poissa käytöstä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Käynnissä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation>Vientisäännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation>Tuontisäännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation>Vie tapahtumat CSV-tiedostoon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation>Lopeta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Yhteydet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Pudotetut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Mikä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch-verkkotilastot {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch-verkkotilastot {0}:lle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation>Yksityiskohdat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation>Säännöt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation>Uusi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation>Toiminto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Poista käytöstä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Ota käyttöön</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Poista</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Muokkaa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Hae kohteeseen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation>Vie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Salli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Estä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Hylkää</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Aina</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Uudelleenkäynnistykseen asti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Kaksoiskappaleet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation>Leikepöydälle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation>Levylle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Oletko varma?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation>Valitse hakemisto, johon säännöt viedään</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Olet poistamassa tätä sääntöä.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    Olet poistamassa tätä merkintää.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Sääntöä ei löydy kyseisellä nimellä ja solmulla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Virhe:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Varoitus:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    Olet poistamassa tätä solmua.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Virhe poistaessa solmua&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Olet poistamassa tätä sääntöä.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation>Virhe vietäessä sääntöjä</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>Valitse hakemisto, jossa on tuotavia sääntöjä (JSON-tiedostot)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation>Säännöt tuotu hyvin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Tallenna CSV:nä</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts b/ui/i18n/locales/fr_FR/opensnitch-fr_FR.ts
new file mode 100644 (file)
index 0000000..5ac785e
--- /dev/null
@@ -0,0 +1,3388 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fr">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>ID utilisateur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Exécuté depuis&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>TextLabel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>IP source</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>ID processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>IP destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>interface destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>depuis cet exécutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>depuis cette ligne de commande</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>vers cette interface</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>cet utilisateur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>vers cette IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>une seule fois</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation type="unfinished">30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation type="unfinished">5mn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation type="unfinished">15mn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation type="unfinished">30mn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation type="unfinished">1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>définitivement</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation type="unfinished">Refuser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Permettre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>jusqu'au redémarrage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished">Activer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished">Effacer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished">Enregistrer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Préférences</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>IU</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>attente par défaut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>attente dialogue par défaut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>attente par défaut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>cible par défaut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>centré</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>en haut à droite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>en bas à gauche</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>en haut à gauche</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>en bas à gauche</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>par l'exécutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>par la ligne de commande</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>par l'interface de destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>par l'IP de destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>par cet utilisateur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>une seule fois</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation type="unfinished">30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation type="unfinished">5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation type="unfinished">15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation type="unfinished">30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation type="unfinished">1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>définitivement</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>Refuser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>Autoriser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Pas de dialogue, montrer juste une alerte</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>noeuds</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>méthode de surveillance des processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;La durée par défaut concerne le cas où aucun utilisateur n'est connecté.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adresse du noeud.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// requise si c'est un socket Unix)&lt;/p&gt;&lt;p&gt;Peut aussi être une adresse IP avec son port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>détail noté dans le log par défaut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;L'action par défaut se déclenche s'il n'y a pas d'utilisateur connecté.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;fichier dans lequel enregistrer le log&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout imprime les logs dans le standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Fichier log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>nom d'hôte (host)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>jusqu'au redémarrage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>toujours</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>appliquer la configuration à tous les noeuds</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Base de données</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>En mémoire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Fichier</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Fermer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Appliquer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Enregistrer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>jusqu'au redémarrage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>type de base de données</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Choisir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Montrer par défaut la vue détaillée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Action</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Sélectionner pour que les dialogues s'ouvrent avec le détail avancé.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Durée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Par défaut un nouveau dialogue en version simple propose de filtrer les connexions ou les applications sur une propriété de la connexion (exécutable, port, IP, etc).&lt;/p&gt;&lt;p&gt;Avec ces options, vous pouvez choisir plusieurs critères pour filtrer les connexions.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Filtrer aussi les connexion par :</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>ID utilisateur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>interface (port) de destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>IP de destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ctte durée est l'attente par défaut lorsqu'un dialogue est présenté.&lt;/p&gt;&lt;p&gt;Sans réponse au dialogue, les options par défaut sont appliquées.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>La vue avancée permet de choisir facileent plusieurs critères pour filtrer les connexions</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>cocher pour que ce champ soit préselectionné lorsqu'un dialogue est affiché</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Action par défaut du dialogue.&lt;/p&gt;&lt;p&gt;Lorsqu'un nouveau dialogue est affiché, cette action sera présélectionnée, et donc appliquée s'il n'y a pas de réponse après le délai par défaut.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Pendant que le dialogue demande si on autorise ou non la connexion:&lt;/p&gt;&lt;p&gt;1. toute autre nouvelle demande de connexion est refusée.&lt;/p&gt;&lt;p&gt;2. les connexions déjà connues sont autorisée ou rejetées selon les règles définies par l'utilisateur.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Action par défaut lorsque l'interface graphique utilisateur n'est pas connectée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Noter (debug) les connexions invalides</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Dialogues</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Options par défaut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Position par défaut sur l'écran</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>toute règle temporaire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Lorsque cette option est choisie, les règles de la durée sélectionnée ne sont pas ajoutées à la liste des règles temporaires présentées dans l'interface graphique utilisateur.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Les règles temporaires sont cependant toujours valides et peuvent être utilisées lors d'une demande d'autorisation /refus de connexion.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Ne pas enregistrer les règles de cette durée :</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Temps</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protocole</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Noeud</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;cochez ceci pour recevoir une demande d'autorisation/refus sur les connexions qui n'ont pas de processus (PID) associé, généralement parce que la connexion est en mauvais état.&lt;/p&gt;&lt;p&gt;Le dialogue ne contiendra alors que l'information sur la connexion.&lt;/p&gt;&lt;p&gt;Il y a cependant des scénarios pour lesquels ces demandes sont valides, comme par exemple l'établissement d'un VPN utilisant wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>colonnes évènements</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Détail processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>chargement...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>chargement CWD...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>chargement statistiques mémoire...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Etat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Fichiers ouverts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>Statistiques entrées/sorties</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Fichiers mappés en mémoire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Pile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Variables d'environnement</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>PIDs application</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Démarrer ou arrêter la surveillance de ce processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Fermer</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Noeud</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Appliquer la règle à tous les noeuds</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Depuis cette ligne de commande</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Depuis cet exécutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Action</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/chemin/vers/executable, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>vers cette IP / ce réseau</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>une seule fois</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/>
+        <source>30s</source>
+        <translation type="obsolete">30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/>
+        <source>5m</source>
+        <translation type="obsolete">5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/>
+        <source>15m</source>
+        <translation type="obsolete">15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/>
+        <source>30m</source>
+        <translation type="obsolete">30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/>
+        <source>1h</source>
+        <translation type="obsolete">1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>toujours</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>vers cette interface (port)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>depuis cet utilisateur (ID)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>ni virgules ni espaces ne sont autorisés pour spécifier de multiples domaines. 
+
+Utiliser à la place des 'expressions régulières' (RegExp): 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+ou bien un seul domaine:
+www.gnu.org - correspond uniquement à www.gnu.org, mais pas ftp.gnu.org, ou www2.gnu.org, ...
+gnu.org         - correspond uniquement à gnu.org, mais pas www.gnu.org, ou ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domain.org, .*\.domain.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Seuls TCP, UDP ou UDPLITE sont permis&lt;/p&gt;&lt;p&gt;On peut employer des expressions régulières (regexp), par ex.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>On peut spécifier une IP unique:
+- 192.168.1.1
+
+ou une &quot;expression régulière&quot;:
+- 192\.168\.1\.[0-9]+
+pluseurs IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+On peut aussi spécifier un sous-réseau:
+- 192.168.1.0/24
+
+Note : on ne peut pas utiliser virgules ou espaces pour séparer plusieurs IP ou réseaux.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/>
+        <source>LAN</source>
+        <translation type="obsolete">LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Durée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protocole</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>Vers cet hôte</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Refuser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Autoriser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Nom</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Activer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Les règles étant appliquées par ordre aplhabétique, on peut leur donner priorité grâce à leur nom.
+
+000-allow-localhost
+001-deny-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">ne pas remplir va créer automatiquement</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Cocher pour que cette règle ait priorité sur toutes les autres. Aucune autre règle ne sera appliquée après celle-ci.
+
+Vous devez nommer la règle de façon qu'elle soit testée en premier, par ordre alphabétique. Par exemple:
+
+[x] Priority - 000-règle-prioritaire
+[  ] Priority - 001-règle-moins-prioritaire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Règle prioritaire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Par défaut, le champ des règles n'est pas sensible à la casse. Par exemple si un processus tente d'atteindre gOOgle.CoM alors que vous avez une règle interdisant .*google.com, la connexion gOOgle.CoM sera bloquée.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Si vous cochez ceci, vous devrez spécifier la chaîne exacte (domaine, exécutable, ligne de commande) que vous voulez filtrer.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>sensible à la casse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Vous pouvez spécifier plusieurs ports d'interface avec des &quot;expessions régulières&quot;:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 ou 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 ou 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>jusqu'au redémarrage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Vers cette liste de domaines</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Choisir un dossier contenant des listes de domaines à autoriser ou interdire.&lt;/p&gt;&lt;p&gt;Y mettre des fichiers contenant des listes de domaines, avec n'importe quelle extension.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Le format de chaque entrée (hist format) est comme suit:&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;ou &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation type="unfinished">Applications</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>Statistiques réseau Opensnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Enregistrer au format CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">contrôle-S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Créer une nouvelle règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Etat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Démarre ou stoppe l'interception</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Evènements</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filtre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Autoriser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Refuser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Ex.: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Noeuds</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double cliquer sur la colonne Addr pour voir les détails d'un noeud)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Règles</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>activer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">rechercher par nom de règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Règle d'applications</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Permanent</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Temporaire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Hôtes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double clic pour voir les détails d'un élément)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Applications</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adresses</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Ports (interfaces)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Utilisateurs</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Connexions</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Abandonnée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>durée active (uptime)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Effacer tous les évènments interceptés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Editer règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Effacer règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Effacer tous les hôtes interceptés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Effacer toutes les applications interceptées</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Effacer toutes les adresses interceptées</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Effacer tous les ports interceptés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Effacer tous les utilisateurs interceptés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double clic sur une ligne pour voir les détails d'une règle)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Effacer les connexions qui déclenchaient cette règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Toutes les applications</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statistiques</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Aide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Fermer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Activer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Désactiver</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished">Configuration appliquée.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished">IP source</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished">Alerte</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Autoriser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Refuser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>indéfiniment</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Connexion sortante</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Processus lancé par :</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>depuis cette ligne de commande</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>depuis cet exécutable</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>jusqu'au redémarrage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>vers le port {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>vers {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>de l'utilisateur {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>vers {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>vers *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">vers *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>processus &lt;b&gt;Distant&lt;/b&gt; %s tournant sur &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>se connecte à &lt;b&gt;%s&lt;/b&gt; sur %s port %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>tente de résoudre (connecter) &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>L'adresse du serveur ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Configuration appliquée.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Config enregistrant les exceptions : {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Applique la configuration à {0} ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Erreur au chargement configuration {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Erreur en tentant d'appliquer la configution : {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Alerte</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Vous devez sélectionner un fichier pour la base de données&lt;br&gt;ou choisir le type &quot;en mémoire&quot;.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>type de base de données changé</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Prendra effet au redémarrage de l'interface graphique</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Glisser la souris sur les textes pour afficher l'aide&lt;br&gt;&lt;br&gt;N'hésitez pas à visiter le Wiki : &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Erreur au chargement d'information sur le processus:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Erreur en stoppant le processus de monitoring:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>chargement...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Aucun noeud n'est connecté.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Règle appliquée.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>le protocole ne peut être vide, ou bien le désélectionner</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Erreur à l'interprétation du RegExp protocole</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>le chemin vers le processus ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>erreur RegExp dans le chemin vers le processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>la ligne de commande ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Erreur RegExp dans la ligne de commande</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>l'interface (port) destination ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>ereur RegExp sur l'interface (port) de destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>l'hôte de destination ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>erreur RegExp sur l'hôte de destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>l'IP / réseau de destination ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>erreur RegExp sur l'IP destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>l'ID utilisateur ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>erreur RegExp sur l'ID utilisateur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Erreur en appliquant la règle : {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>le champ &quot;listes&quot; ne peut être vide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>le champ Listes doit être un répertoire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;RRègle non supportée&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Erreur au chargement de la règle&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Inactif</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>désactivé</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Actif</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Vous êtes sur le point d'effacer cette règle.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Êtes-vous sûr?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>Statistiques réseau OpenSnitch {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>Statistique réseau OpenSnitch pour {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation type="unfinished">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Enregistrer en CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Effacer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Désactiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Activer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Dupliquer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Editer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Pas trouvé de règle pour ces nom et noeud</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Erreur:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Alerte :</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Autoriser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Refuser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Toujours</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Jusqu'au redémarrage</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Vous êtes sur le point d'effacer cette règle.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="174"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">Última Conexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nom</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Etat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nom d'hôte</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Version</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Règles</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Temps</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Action</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Durée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Noeud</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Activé</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Connexions</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protocole</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Processus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Destination</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Règle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>ID utilisateur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DernièreConnexion</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Args</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstHost</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstPort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="179"/>
+        <source>Uptime</source>
+        <translation type="obsolete">durée active (uptime)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Connexions</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Abandonnée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">durée active (uptime)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Connexions</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Abandonnée</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts b/ui/i18n/locales/hu_HU/opensnitch-hu_HU.ts
new file mode 100644 (file)
index 0000000..cea0fbe
--- /dev/null
@@ -0,0 +1,3251 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="hu_HU">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="150"/>
+        <source>Chromium Web Browser</source>
+        <translation type="obsolete">Chromium webböngésző</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="179"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;/opt/google/chrome/bin/chrome --something abc --more-long  def --for-word-wrapping&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;/opt/google/chrome/bin/chrome --valami ábécé --hosszabb-ideig  def --szócsomagoláshoz&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="226"/>
+        <source>(/path/to/bin/chromium)</source>
+        <translation type="obsolete">(/út/a/bináris/Chromiumhoz)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>ettől a futtatható fájltól</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>erről a parancssorról</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>ezt célkikötőt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>ezt a felhasználót</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>ezt a cél IP címet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>egyszer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30 másodperc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1 óra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>újraindításig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>örökre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Megtagadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>Felhasználói azonosító</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Futtatható fájl innen:&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>Szövegcímke</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Forrás IP-cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>Folyamatazonosító</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Cél IP-címe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Célkikötő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="271"/>
+        <source>Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</source>
+        <translation type="obsolete">A Chromium webböngésző csatlakozni akar a www.evilsocket.net webhelyhez a 443-as TCP porton. És talán a www.goodsocket.net webhelyhez a 344-es TCP porton</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>ebből a folyamatazonosítóból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Tűzfal</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Tűzfal&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>Bejövő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>Kimenő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Profil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>Bejövő kapcsolatok engedélyezése egy kikötőhöz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>Szolgáltatás engedélyezése (BE)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>A kikötőhöz tartó kimenő kapcsolatok kizárása az elfogásból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation>Szolgáltatás engedélyezése (KI)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation>Új szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="453"/>
+        <source>xxx</source>
+        <translation type="obsolete">xxx</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation>Bezárás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Tűzfalszabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Csomópont</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="34"/>
+        <source>All</source>
+        <translation type="obsolete">Összes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>Leírás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>Egyszerű</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>Új feltétel hozzáadása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>Kiválasztott feltétel eltávolítása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation>Irány</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation>BE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation>KI</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation>Művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation>ELFOGADÁS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation>KIDOBÁS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation>ELUTASÍTÁS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation>VISSZATÉRÉS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation>Kiürítés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation>Törlés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation>Mentés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation>Hozzáadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Beállítások</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Felhasználói felület</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Az előugró ablakok alapértelmezett beállításai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Felugró ablakok alapértelmezett helye a képernyőn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Alapértelmezés szerint a haladó nézet megjelenítése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>egyszer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30 másodperc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1 óra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>újraindításig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>örökre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Felugró ablak alapértelmezett művelete</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Alapértelmezett cél</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ha be van jelölve, az előugró ablakok aktív haladó nézettel jelennek meg.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>megtagadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>futtatható fájl szerint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>parancssor szerint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>célkikötő szerint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>rendeltetési hely IP címe szerint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>felhasználói azonosító szerint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>középre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>jobb felső</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>jobb alsó</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>bal felső</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>bal alsó</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Előugró ablak alapértelmezett időtartama</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Időtartam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Alapértelmezés szerint, amikor egy új előugró ablak jelenik meg, a legegyszerűbb formájában képes lesz a kapcsolatok vagy alkalmazások szűrésére a kapcsolat egy tulajdonságával (futtatható fájl, kikötő, IP-cím stb.).&lt;/p&gt;&lt;p&gt;Ezekkel az opciókkal több mezőt is választhat, amelyekhez a kapcsolatokat szűrni kívánja.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Szűrje a csatlakozásokat az alábbiak szerint is:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>Felhasználói azonosító</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Célkikötő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Cél IP-címe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Tiltsa le a előugró elemet, csak riasztást jelenítsen meg</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ez az időkorlát az a visszaszámlálás, amelyet akkor láthat, amikor egy felbukkanó párbeszédpanel jelenik meg.&lt;/p&gt;&lt;p&gt;Ha nem válaszol a felbukkanó párbeszédpanelre, akkor az alapértelmezett beállítások kerülnek alkalmazásra.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Alapértelmezett időtúllépés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Csomópontok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Folyamatfigyelés módszer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Naplófájl a naplók írásához.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;A /dev/stdout naplókat nyomtat a normál kimenetre.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Naplófájl</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Az alapértelmezett időtartam akkor kerül végrehajtásra, ha nincs csatlakoztatva felhasználói felület.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Alapértelmezett időtartam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Beállítások alkalmazása az összes csomópontra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Az alapértelmezett művelet akkor kerül végrehajtásra, ha nincs csatlakoztatva felhasználói felület.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Alapértelmezett művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Állomásnév</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="671"/>
+        <source>proc</source>
+        <translation type="obsolete">proc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="676"/>
+        <source>ebpf</source>
+        <translation type="obsolete">ebpf</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="681"/>
+        <source>audit</source>
+        <translation type="obsolete">audit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="686"/>
+        <source>ftrace</source>
+        <translation type="obsolete">ftrace</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>újraindításáig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>mindig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A csomópont címe.&lt;/p&gt;&lt;p&gt;Alapértelmezett: unix:///tmp/osui.sock (Az „unix://” kötelező, ha Unix szoftvercsatorna)&lt;/p&gt;&lt;p&gt;Lehet IP-cím is a kikötővel: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ha be van jelölve, az OpenSnitch több okból is meg fogja engedni vagy megtagadja azokat a kapcsolatokat, amelyek nem rendelkeznek társított folyamatazonosítóval.&lt;/p&gt;&lt;p&gt;A felbukkanó párbeszédpanel csak a hálózati kapcsolatról tartalmaz információkat.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Ismeretlen kapcsolatok elfogása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Változat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="778"/>
+        <source>DEBUG</source>
+        <translation type="obsolete">HIBAKERESÉS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="783"/>
+        <source>INFO</source>
+        <translation type="obsolete">TÁJÉKOTTATÁS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="788"/>
+        <source>IMPORTANT</source>
+        <translation type="obsolete">FONTOS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="793"/>
+        <source>WARNING</source>
+        <translation type="obsolete">FIGYELMEZTETÉS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="798"/>
+        <source>ERROR</source>
+        <translation type="obsolete">HIBA</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="803"/>
+        <source>FATAL</source>
+        <translation type="obsolete">VÉGZETES</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Alapértelmezett naplószint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Adatbázis</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Adatbázistípus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Kijelölés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>Memóriabeli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Fájl</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Bezárás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Alkalmazás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Mentés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>A haladó nézet lehetővé teszi több mező egyszerű kiválasztását a kapcsolatok szűréséhez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Ha be van jelölve, akkor ez a mező lesz kiválasztva, amikor egy előugró jelenik meg</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Előugró ablak alapértelmezett művelete.&lt;/p&gt;&lt;p&gt;Amikor új kimenő kapcsolat jön létre, ez a művelet alapértelmezés szerint ki lesz választva, tehát ha az időtúllépés aktiválódik, akkor ez az opció lesz alkalmazva.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Miközben egy előugró ablak kéri a felhasználót, hogy engedélyezze vagy tagadja meg a kapcsolatot:&lt;/p&gt;&lt;p&gt;1. megtagadják az új kimenő kapcsolatokat.&lt;/p&gt;&lt;p&gt;2. az ismert kapcsolatok a felhasználó által meghatározott szabályok alapján engedélyezhetők vagy megtagadhatók.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Alapértelmezett művelet a grafikus felhasználói felület leválasztása esetén</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Érvénytelen kapcsolatok hibakeresése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Előugró ablakok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Alapértelmezett beállítások</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Alapértelmezett hely a képernyőn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>bármilyen ideiglenes szabályt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ha ezt az opciót választja, a kiválasztott időtartam szabályai nem kerülnek hozzáadásra a grafikus felhasználói felület ideiglenes szabályainak listájához.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Az ideiglenes szabályok továbbra is érvényesek, és használhatja őket, amikor a rendszer kéri az új kapcsolat engedélyezését/elutasítását.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Ne mentse az időtartam szabályait</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Idő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Cél</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Folyamat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Csomópont</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ha be van jelölve, az OpenSnitch felszólítja Önt, hogy engedélyezze vagy utasítsa el azokat a kapcsolatokat, amelyek nem rendelkeznek aszocizált folyamatazonosítóval, több okból, főleg a rossz állapotú kapcsolatok miatt.&lt;/p&gt;&lt;p&gt;Az előugró párbeszédablak csak a hálózati kapcsolatra vonatkozó adatokat tartalmazza.&lt;/p&gt;&lt;p&gt;Vannak azonban olyan esetek, amikor ezek érvényes kapcsolatok, például amikor virtuális magánhálózatot hoznak létre a WireGuard használatával.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Események lap oszlopai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>folyamatazonosító által</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Előugró ablakok letiltása, csak értesítés megjelenítése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Asztali értesítések</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Rendszerértesítések használata</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Qt-értesítések használata</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation>Tesztelés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ha be van jelölve, az OpenSnitch felkéri, hogy engedélyezze vagy tiltsa le azokat a kapcsolatokat, amelyekhez nem tartozik folyamatazonosító, több okból is, főleg a rossz állapotú kapcsolatok miatt.&lt;/p&gt;&lt;p&gt;A felugró párbeszédpanel csak a hálózati kapcsolatra vonatkozó információkat tartalmaz.&lt;/p&gt;&lt;p&gt;Vannak olyan esetek, amikor ezek érvényes kapcsolatok, például virtuális magánhálózat WireGuard segítségével történő létesítésekor.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation>Eseménytisztítások közötti percek száma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>nap</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Események megtartására nyitva álló napok száma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>elutasítás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation>Rendszer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation>Parancssor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Téma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation>Szabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation>Ha ez a lehetőség be van jelölve, a kiválasztott időtartamra vonatkozó szabályok nem lesznek hozzáadva az ideiglenes szabályok listájához a grafikus felhasználói felületen.
+
+Az ideiglenes szabályok továbbra is érvényesek maradnak, és használhatja őket, amikor a rendszer kéri, hogy engedélyezze/megtagadja az új kapcsolatot.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>Ne mentse/törölje az időtartamra vonatkozó szabályokat:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation>30 másodperc vagy kevesebb</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation>5 perc vagy kevesebb</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation>15 perc vagy kevesebb</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation>30 perc vagy kevesebb</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation>1 óra vagy kevesebb</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Folyamat részletei</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>betöltés…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: betöltés…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>memória statisztika: betöltés…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Állapot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Fájlok megnyitása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O statisztika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Memóriába ágyazott fájlok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Verem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Környezeti változók</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Alkalmazás folyamatazonosítók</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Folyamatfigyelés indítsa/leállítása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Bezárás</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Csomópont</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Alkalmazzon szabályt minden csomópontra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>Erre az IP-címre vagy a hálózatra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/útvonal/a/futtathatóhoz, .*/bináris/végrehajtható[0-9\.]+$, …</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>Erre a kikötőre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Ehhez a tartománylistához</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Megadhat egyetlen IP-címet:
+- 192.168.1.1
+
+vagy reguláris kifejezés:
+- 192\.168\.1\.[0-9]+
+
+több IP-cím:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Megadhat alhálózatot is:
+- 192.168.1.0/24
+
+Megjegyzés: Vesszőkkel vagy szóközökkel nem szabad elválasztani az IP-címeket vagy a hálózatokat.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/>
+        <source>LAN</source>
+        <translation type="obsolete">Helyi hálózat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="464"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="469"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="474"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="484"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="489"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="494"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="499"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="504"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="509"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="514"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="519"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="524"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Több kikötő megadható reguláris kifejezések használatával:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 vagy 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 vagy 5551, 5552, 5553, stb:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>egyszer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/>
+        <source>30s</source>
+        <translation type="obsolete">30 másodperc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/>
+        <source>5m</source>
+        <translation type="obsolete">5 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/>
+        <source>15m</source>
+        <translation type="obsolete">15 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/>
+        <source>30m</source>
+        <translation type="obsolete">30 perc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/>
+        <source>1h</source>
+        <translation type="obsolete">1 óra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>újraindításig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>mindig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Vesszők vagy szóközök nem engedélyezhetnek több tartomány megadását. 
+
+Használjon helyette reguláris kifejezéseket: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+vagy egyetlen tartomány:
+www.gnu.org - csak a www.gnu.org, nem az ftp.gnu.org, a www2.gnu.org, …
+gnu.org         - csak a gnu.org-nak fog megfelelni, nem a www.gnu.org-nak, nem az ftp.gnu.org-nak, …</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.tartomány.hu, .*\.tartomány.hu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>Erre az állomásra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Időtartam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Csak TCP, UDP vagy UDPLITE engedélyezett&lt;/p&gt;&lt;p&gt;Használhatja a szabályos kifejezést, azaz: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Ebből a futtatható fájlból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Megtagadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Ebből a parancssorból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Ebből a felhasználói azonosítóból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Válasszon egy címtárat a letiltáshoz vagy engedélyezéshez szükséges tartománylistákkal.&lt;/p&gt;&lt;p&gt;Helyezze be a címtár fájlokat bármilyen kiterjesztéssel, amely tartalmazza a tartományok listáit.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;A lista minden bejegyzésének formátuma a következő (állomás formátum): &lt;/p&gt;&lt;p&gt;127.0.0.1 www.tartomány.hu&lt;/p&gt;&lt;p&gt;vagy &lt;/p&gt;&lt;p&gt;0.0.0.0 www.tartomány.hu&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Név</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>A szabályokat ábécé sorrendben ellenőrzik, így azok prioritása szerint ennek megfelelően nevezheti meg őket.
+
+000-helyi-állomás-engedélyezése
+001-közvetítés-tagadása
+…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">hagyja üresen az önműködő létrehozáshoz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Ha be van jelölve, akkor ez a szabály elsőbbséget élvez a többi szabálygal szemben. Ez után más szabályokat nem fogunk ellenőrizni.
+
+A szabályt úgy kell megneveznie, hogy először ellenőrizni fogják, mert betűrendben ellenőrzik. Például: 
+
+[x] Elsőbbség - 000-elsőbbségi-szabály
+[  ] Elsőbbség - 001-kevésbé-elsőbbségi-szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Elsőbbségi szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Alapértelmezés szerint a szabályok mezője nem különbözteti meg a kis- és nagybetűket, azaz ha egy folyamat megpróbálja elérni a gOOgle.CoM tartományt, és van egy szabályod, amelyet meg kell tagadni a .*google.com tartomány, a kapcsolat letiltva lesz.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Ha bejelöli ezt a jelölőnégyzetet, meg kell adnia azt a pontos karakterláncot (tartomány, futtatható fájl, parancssor), amelyet szűrni szeretne.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Kis- és nagybetűk felismerése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Alkalmazások</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ez a mező tartalmazza a felhasználó által végrehajtott parancssort, és megegyezik vele.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Ha a felhasználó beírta a parancsot, csak a parancsot megjelenik:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Ha a felhasználó beírta a parancs abszolút vagy relatív elérési útját, akkor ez fog megjelenni:&lt;/p&gt;&lt;p&gt; &gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>Ebből a folyamatazonosítóból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Hálózat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation>Tartományok/IP-címek listája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation>Ehhez a hálózati tartományok listájához</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation>Ehhez az IP-címlistához</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Válasszon ki egy könyvtárat a tiltandó vagy engedélyezendő IP-címek listáját tartalmazó fájlokkal:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4. 6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;stb.&lt;/p&gt;&lt;p&gt;Soronként egy IP-cím. Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Válasszon ki egy könyvtárat a tiltandó vagy engedélyezni kívánt hálózati tartományok listáját tartalmazó fájlokkal:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0 /20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;stb.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Egy hálózati tartomány soronként. Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Válasszon ki egy könyvtárat a tiltandó vagy engedélyezni kívánt tartományok listájával.&lt;/p&gt;&lt;p&gt;A könyvtárba helyezzen be olyan fájlokat, amelyek a tartomány listáit tartalmazzák.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;A lista minden egyes bejegyzésének formátuma a következő (gazdaformátum):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.tartomány.com&lt;/p&gt;&lt;p&gt;vagy &lt;/p&gt;&lt;p&gt;0.0.0.0 www.tartomány.com&lt;/p&gt;&lt;p&gt;Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>Ehhez a tartománylistához
+(szabályos kifejezések)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Válasszon ki egy könyvtárat a tiltandó vagy engedélyezendő tartományok szabványos kifejezéseit tartalmazó fájlokkal:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt; Használhat olyan tartományt is, amilyen: &amp;quot;example.com&amp;quot;, és egyezik a bármi.példa.com, bármi.példa.com.helyitartomány stb. oldallal.&lt;/p&gt;&lt;p&gt;Soronként egy tartomány. Az üres sorokat vagy a # karakterrel kezdődő sorokat figyelmen kívül hagyja.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Elutasítás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation>Leírás…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;E mező értéke mindig a végrehajtható fájl abszolút elérési útja: /útvonal/a/binárishoz&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Példák:&lt;/p&gt;&lt;p&gt;- Egyszerű: /útvonal/a/binárishoz&lt;/p&gt;&lt;p&gt;- Több elérési út: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Több bináris fájl: ^( /usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- A /tmp fájl végrehajtásának megtagadása/engedélyezése:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;További példákért keresse fel a &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki oldalon&lt;/a&gt;, vagy érdeklődjön a &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;vitafórumokon&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>Szabályos kifejezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation>szabályos kifejezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation>Hálózati felület</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation>Több</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>Ne naplózza azokat a kapcsolatokat, amelyek megfelelnek ennek a szabálynak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation>Ne naplózza a kapcsolatokat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Megtagadás csak elveti a kapcsolatot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Elutasítás megszakítja a kapcsolatot, és leállítja azt a szoftvercsatornát, amelyik kezdeményezte</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>Engedélyezés lehetővé teszi a kapcsolatot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="83"/>
+        <source>Name (leave blank to autocreate)</source>
+        <translation type="obsolete">Név (üres hagyása az automatikus létrehozáshoz)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation>ICMP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation>ICMP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation>SCTP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation>SCTP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="937"/>
+        <source>Color</source>
+        <translation type="obsolete">Szín</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch hálózati statisztika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Mentés CSV formátumban…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Új szabály létrehozása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;állomásnév - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Állapot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Adatelérés indítása/leállítása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Események</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Szűrő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Megtagadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Például: Firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Az összes elfogott esemény törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Csomópontok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(kattintson duplán a Cím oszlopra a csomópont részleteinek megtekintéséhez)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Szabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Szabály szerkesztése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Szabály törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(kattintson duplán egy sorra a szabály részleteinek megtekintéséhez)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">szabálynév keresése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Alkalmazási szabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Állandó</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Ideiglenes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Gazdagépek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(kattintson duplán az elem részleteinek megtekintéséhez)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Az összes elfogott gazdagép törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Alkalmazások</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Az összes elfogott alkalmazás törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Címek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Az összes elfogott cím törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Kikötők</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Az összes elfogott port törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Felhasználók</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Az összes elfogott felhasználó törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Kapcsolatok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Elvetve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Hasznos üzemidő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Változat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Kapcsolat törlése amelyek megfelelnek ennek a szabálynak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Minden alkalmazás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Elutasítás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation>Rendszer szabályai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation>Csomópont törlése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation>Csomópont-beállítások megjelenítése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation>Csomópont adatelérés indítása/leállítása</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statisztika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Letiltás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Súgó</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Bezárás</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation>Beállítás alkalmazva.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation>Hiba: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation>Módosítások alkalmazása…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>Hiba történt a BEMENET láncszabályzat lekérésekor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>Hiba történt a KIMENET láncszabályzat lekérésekor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>A tűzfalszabályok grafikus felhasználói felületről történő beállításához az „iptables” helyett az „nftables”-t kell használni</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation>Tűzfal engedélyezése…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation>Tűzfal letiltása…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Célkikötő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Forráskikötő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>Cél IP-cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>Forrás IP-cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>Bemeneti felület</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>Kimeneti felület</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>conntrack-jelölés beállítása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>conntrack-jelölés egyezése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>conntrack-egyezés állapot(ok)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>Jelölés beállítása a csomagon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>Csomagadatok egyeztetése</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>Sávszélesség-kvóták</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>Sebességkorlát csatlakozások</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>A protobuf verziója nem kompatibilis, telepítenie kell a protobuf 3.8.0 vagy újabb verzióját
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation>Szabály törölve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation>Szabály hozzáadva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation>A ',' vagy '-' karakterekkel több kikötők/IP-címet vagy tartományt/értéket adhat meg:&lt;br&gt;&lt;br&gt;kikötők: 22 vagy 22,443 vagy 50000-60000&lt;br&gt;IP-címek: 192.168.1.1 vagy 192.168 .1.30-192.168.1.130&lt;br&gt;Értékek: echo-reply,echo-request&lt;br&gt;Értékek: new,established,related</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation>Szabály törlése, kérjük, várjon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation>Hiba történt a szabály frissítésekor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation>Szabály hozzáadása, kérjük, várjon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation>&lt;válasszon kijelentést&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation>Egyenlő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation>Nem egyenlő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation>Egyenlő vagy nagyobb, mint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation>Nagyobb, mint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation>Egyenlő vagy kisebb, mint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation>Kisebb, mint</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation>Tűzfalszabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation>Egyszerű</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation>Haladó</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation>Ez a szabály még nem támogatott.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation>Szolgáltatás kizárása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>Bejövő kapcsolatok engedélyezése a kiválasztott kikötőhöz.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>Kimenő kapcsolatok engedélyezése a kiválasztott kikötőhöz.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation>válasszon kijelentést.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>az érték nem lehet 0 vagy üres.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>az érték formátuma 1024/kbájt (vagy bájt, mbájt, gbájt)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>az érték formátuma 1024/kbájt/másodperc (vagy bájt, mbájt, gbájt)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>a sebességkorlát nem érvényes, használja: bájt, kbájt, mbájt vagy gbájt.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>időkorlát nem érvényes, használja: másodperc, perc, óra vagy nap</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation>kikötő nem érvényes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation>Adat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation>Hiba</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation>Figyelmeztetés</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>A rendszerértesítések nem érhetők el, telepítenie kell a python3-notify2-t.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>újraindításig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>örökre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Megtagadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Kimenő kapcsolat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Folyamat innen indult:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>ettől a végrehajtható fájlból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>erről a parancssorról</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>kikötőig: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>eddig: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>a(z) {0} felhasználótól</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>eddig: {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>eddig: *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">eddig: *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>A(z) %s &lt;b&gt;távoli&lt;/b&gt; folyamat fut a(z) &lt;b&gt;%s&lt;/b&gt;-n</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>csatlakozik &lt;b&gt;%s&lt;/b&gt;-hoz a %s-kikötőn %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>megpróbálja megoldani a(z) &lt;b&gt;%s&lt;/b&gt; problémát a(z) %s segítségével, %s-kikötő %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>ebből a folyamatazonosítóból</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation>Új kimenő kapcsolat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Elutasítás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>csatlakozik a következőhöz: &lt;b&gt;%s&lt;/b&gt;, %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Beállítás mentése kivétele: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Figyelmeztetés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Válasszon egy fájlt az adatbázishoz&lt;br&gt;vagy válassza a „Memóriabeli” típust.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>DB típusa megváltozott</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Indítsa újra a grafikus felhasználói felületet, hogy a hatások életbe léphessenek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Beállítások alkalmazása: {0}…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Kiszolgáló címe nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Hiba történt a(z) {0}-beállítás betöltésekor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Beállítás alkalmazva.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Hiba a beállítás alkalmazásakor: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>A súgó megjelenítéséhez vigye az egeret a szövegek fölé&lt;br&gt;&lt;br&gt;Emlékezzen meglátogatni a wikit: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation>Rendszer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>A témák nem állnak rendelkezésre. Telepítse a qt-material-t: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation>A felhasználói felület témája megváltozott</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Indítsa újra a grafikus felhasználói felületet az új téma alkalmazásához</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation>Rendben</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="472"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation type="obsolete">Indítsa újra a grafikus felhasználói felületet, hogy a változtatások életbe lépjenek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>Nincsenek csomópontok összekapcsolva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>Kivétel menti a(z) {0} csomópont beállítását: {1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Hiba a folyamatadatok betöltésekor:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Hiba a megfigyelési folyamat leállításakor:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>betöltés folyamatban…</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Nincsenek csomópontok csatlakoztatva.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>A szabály alkalmazva.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Hiba a szabály alkalmazásakor: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Hiba történt a szabály betöltésekor&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>a protokoll nem lehet üres, és nem szüntetheti meg a kijelölést</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Protokoll szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>folyamat útvonal nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Folyamat útvonala szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>parancssor nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Parancssor szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Célkikötő nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Célkikötő szabványos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Célállomás nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Célállomás szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Cél IP-cím/hálózat nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Cél IP-cím szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Felhasználói azonosító nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Felhasználói azonosító szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Listamező nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Listmezők könyvtárnak kell lennie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;A szabály nem támogatott&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Már van egy szabály ezzel a névvel.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>Folyamatazonosító mező nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>Folyamatazonosító mező szabványos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>Jelöljön ki legalább egy mezőt.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation>Hálózati felület nem lehet üres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation>Hálózati felület szabályos kifejezéshibája</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Név</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Állapot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Állomásnév</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Változat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Szabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Idő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Időtartam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Csomópont</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Engedélyezve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Találatok száma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Nem fut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Letiltva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Futtatás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>{0} OpenSnitch hálózati statisztika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch hálózati statisztikák a következőhöz: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Hiba:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Figyelmeztetés:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Megtagadás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Mindig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Újraindításig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Letiltás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Engedélyezés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Másolás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Szerkesztés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Törlés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    A szabály törlésére készül.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Biztos benne?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>A szabály nem található ezen a néven és csomóponton</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    A szabály törlésére készül.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Mentés CSV-fájlként</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Név</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Név</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Állapot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Állomásnév</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Változat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Szabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Idő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Időtartam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Csomópont</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Engedélyezve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Találatok száma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Név</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Cím</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Állapot</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Állomásnév</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Változat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Szabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Idő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Művelet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Időtartam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Csomópont</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Engedélyezve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Találatok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Folyamat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Cél</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Szabály</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Felhasználóazonosító</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>LegutóbbiCsatlakozás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Argumentumok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>CélIPcíme</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Célállomásneve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Célkikötő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="174"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">LegutóbbiCsatlakozás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="179"/>
+        <source>Uptime</source>
+        <translation type="obsolete">Hasznos üzemidő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Kapcsolatok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Elvetve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Mi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Alkalmazás a következőre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Elutasítás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Hálózatnév</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hasznos üzemidő</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kapcsolatok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Elvetve</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Mi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Sorrend</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Új csomópont csatlakoztatva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Leírás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Parancssor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation>Exportszabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation>Importszabályok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation>Események exportálása CSV-fájlként</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation>Kilépés</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation>Exportálás</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation>A vágólapra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation>Lemezre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation>Exportszabályok könyvtár kiválasztása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    A bejegyzés törlésére készül.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    A csomópont törlésre készül.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Hiba történt a csomópont törlésekor&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation>Hiba történt a szabályok exportálásakor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>Importszabályok (JSON-fájlok) könyvtár kiválasztása</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation>Szabályok sikeresen importálva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished">FIGYELMEZTETÉS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts b/ui/i18n/locales/ja_JP/opensnitch-ja_JP.ts
new file mode 100644 (file)
index 0000000..8e4c748
--- /dev/null
@@ -0,0 +1,3057 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ja_JP">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation type="unfinished">ユーザーID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;実行元&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation type="unfinished">送信元のIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation type="unfinished">プロセスID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation type="unfinished">宛先IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation type="unfinished">宛先ポート</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation type="unfinished">この実行ファイルを</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation type="unfinished">このコマンドラインを</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation type="unfinished">この宛先ポートに対して</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation type="unfinished">このユーザーを</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation type="unfinished">この宛先IPに対して</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation type="unfinished">一度のみ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation type="unfinished">30秒間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation type="unfinished">5分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation type="unfinished">15分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation type="unfinished">30分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation type="unfinished">1時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation type="unfinished">再起動するまで</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation type="unfinished">永久に</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation type="unfinished">拒否する</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation type="unfinished">許可する</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished">アクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation type="unfinished">設定</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation type="unfinished">UI</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation type="unfinished">ダイアログの表示時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation type="unfinished">ポップアップ時に選択される規定のルールの有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation type="unfinished">既定のルール有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">ポップアップ時に選択される規定のアクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">既定のアクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation type="unfinished">既定のターゲット</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation type="unfinished">中央</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation type="unfinished">右上部</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation type="unfinished">右下部</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation type="unfinished">左上部</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation type="unfinished">左下部</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">既定のダイアログ表示位置</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation type="unfinished">実行ファイル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation type="unfinished">コマンドライン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation type="unfinished">宛先ポート</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation type="unfinished">宛先IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation type="unfinished">ユーザーID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation type="unfinished">一度のみ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation type="unfinished">30秒間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation type="unfinished">5分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation type="unfinished">15分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation type="unfinished">30分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation type="unfinished">1時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation type="unfinished">再起動するまで</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation type="unfinished">永久に</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation type="unfinished">拒否</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation type="unfinished">許可</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">ポップアップを無効にして通知のみ表示</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation type="unfinished">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation type="unfinished">プロセス監視方式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;規定のルール有効期間は、UIが接続されていないときに使用されます。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ノードのアドレス&lt;/p&gt;&lt;p&gt;標準: unix:///tmp/osui.sock (Unixソケットの場合はunix://が必須)&lt;/p&gt;&lt;p&gt;このようにIPアドレスとポートを指定することもできます。127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation type="unfinished">アドレス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation type="unfinished">既定のログレベル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation type="unfinished">バージョン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;規定のアクションは、UIが接続されていないときに使用されます。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="671"/>
+        <source>proc</source>
+        <translation type="obsolete">proc</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="681"/>
+        <source>audit</source>
+        <translation type="obsolete">audit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="686"/>
+        <source>ftrace</source>
+        <translation type="obsolete">ftrace</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ファイルにログを記録します&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdoutにすると標準出力にログを出力します&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation type="unfinished">ログファイル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="778"/>
+        <source>DEBUG</source>
+        <translation type="obsolete">DEBUG</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="783"/>
+        <source>INFO</source>
+        <translation type="obsolete">INFO</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="788"/>
+        <source>IMPORTANT</source>
+        <translation type="obsolete">IMPORTANT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="793"/>
+        <source>WARNING</source>
+        <translation type="obsolete">WARNING</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="798"/>
+        <source>ERROR</source>
+        <translation type="obsolete">ERROR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="803"/>
+        <source>FATAL</source>
+        <translation type="obsolete">FATAL</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;有効にした場合、opensnitchは、関連したPIDを持たない接続を許可するか拒否するかを確認します。&lt;/p&gt;&lt;p&gt;ポップアップダイアログには、ネットワーク接続に関する情報のみが表示されます。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">不明なプロセスを検証</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation type="unfinished">ホスト名</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation type="unfinished">unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation type="unfinished">再起動するまで</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation type="unfinished">常に</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation type="unfinished">/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation type="unfinished">/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation type="unfinished">全てのノードに設定を反映</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation type="unfinished">データベース</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation type="unfinished">データベース方式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation type="unfinished">参照</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation type="unfinished">メモリ内</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation type="unfinished">ファイル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation type="unfinished">閉じる</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation type="unfinished">適用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation type="unfinished">保存</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">ポップアップの規定のアクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">規定のポップアップ表示位置</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation type="unfinished">詳細表示では、接続をフィルタリングするために複数のフィールドを簡単に選択できます</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation type="unfinished">標準で詳細表示を有効にする</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation type="unfinished">アクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;有効にすると、詳細表示がアクティブな状態でポップアップが表示されます。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation type="unfinished">ルールの有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ポップアップの表示時、標準では接続の1つのプロパティ (実行可能ファイル、ポート、IP など) によって接続またはアプリケーションをフィルタリングできます。&lt;/p&gt;&lt;p&gt;これらのオプションを使用すると、接続をフィルタリングする際、複数のフィールドを選択できます。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>次を使用して通信をフィルタ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation type="unfinished">有効にすると、ポップアップが表示されたときに、このフィールドが選択されます</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation type="unfinished">ユーザーID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation type="unfinished">宛先ポート</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation type="unfinished">宛先IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;このタイムアウトは、ポップアップダイアログの表示時間のカウントダウンです。&lt;/p&gt;&lt;p&gt;ポップアップに回答しない場合、このオプションが適用されます。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="676"/>
+        <source>ebpf</source>
+        <translation type="obsolete">ebpf</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;ポップアップの規定のアクション&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation type="unfinished">GUI未接続時の規定のアクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation type="unfinished">無効な接続をデバッグ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation type="unfinished">ポップアップ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation type="unfinished">規定のオプション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation type="unfinished">規定の表示位置</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation type="unfinished">全ての一時ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">ルールの有効期間を保持しない</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation type="unfinished">時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation type="unfinished">宛先</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation type="unfinished">プロトコル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation type="unfinished">プロセス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation type="unfinished">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation type="unfinished">イベントタブの項目</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation type="unfinished">プロセス情報</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation type="unfinished">読み込み中...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation type="unfinished">CWD:-読み込み中...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation type="unfinished">メモリ状態: 読み込み中...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation type="unfinished">状態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation type="unfinished">ファイルアクセス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation type="unfinished">入出力の統計</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation type="unfinished">メモリ内データ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation type="unfinished">スタック</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation type="unfinished">環境変数</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation type="unfinished">プロセスID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation type="unfinished">プロセスの監視を開始/停止</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation type="unfinished">閉じる</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation type="unfinished">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation type="unfinished">全てのノードにルールを反映</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation type="unfinished">IP/ネットワーク</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation type="unfinished">アクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation type="unfinished">ポート</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation type="unfinished">ドメインリスト</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="459"/>
+        <source>LAN</source>
+        <translation type="obsolete">LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation type="unfinished">一度のみ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="797"/>
+        <source>30s</source>
+        <translation type="obsolete">30秒間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="802"/>
+        <source>5m</source>
+        <translation type="obsolete">5分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="807"/>
+        <source>15m</source>
+        <translation type="obsolete">15分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="812"/>
+        <source>30m</source>
+        <translation type="obsolete">30分間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="817"/>
+        <source>1h</source>
+        <translation type="obsolete">1時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation type="unfinished">再起動するまで</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation type="unfinished">常に</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation type="unfinished">ホスト</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation type="unfinished">有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation type="unfinished">TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="348"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="353"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="358"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation type="unfinished">プロトコル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation type="unfinished">実行ファイル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation type="unfinished">拒否</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation type="unfinished">許可</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation type="unfinished">コマンドライン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation type="unfinished">ユーザーID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">名前</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation type="unfinished">有効</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">空にすると自動生成されます</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation type="unfinished">優先ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation type="unfinished">大文字/小文字を区別</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="unfinished">OpenSnitchネットワークモニター</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">CSVファイルに保存</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation type="unfinished">ルールを新規作成</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;ホスト名 - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation type="unfinished">状態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation type="unfinished">-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation type="unfinished">サービスを開始/停止</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation type="unfinished">イベント</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation type="unfinished">絞り込み</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation type="unfinished">許可中</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation type="unfinished">拒否中</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation type="unfinished">例:firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation type="unfinished">50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation type="unfinished">100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation type="unfinished">200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation type="unfinished">300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation type="unfinished">記録した全てのイベント履歴を消去</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation type="unfinished">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(項目をダブルクリックでノードの詳細を確認できます)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation type="unfinished">有効</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation type="unfinished">ルールを編集</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation type="unfinished">ルールを削除</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="674"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(項目をダブルクリックでルールの詳細を確認できます)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">ルール名を検索</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation type="unfinished">アプリケーションのルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation type="unfinished">永久ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation type="unfinished">一時ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation type="unfinished">ホスト</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(項目をダブルクリックで詳細を確認できます)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">記録した全てのホストを消去</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation type="unfinished">アプリケーション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">記録した全てのアプリケーション履歴を消去</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation type="unfinished">アドレス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">記録した全てのアドレスを消去</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation type="unfinished">ポート</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">記録した全てのポートを消去</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation type="unfinished">ユーザー</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">記録した全てのユーザー履歴を消去</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation type="unfinished">通過</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation type="unfinished">ブロック</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation type="unfinished">実行時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation type="unfinished">バージョン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(項目をダブルクリックするとルールの詳細が確認できます)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">このルールにマッチした接続を削除します</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation type="unfinished">全てのアプリケーション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation type="unfinished">ダッシュボードを開く</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation type="unfinished">ヘルプ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation type="unfinished">終了</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation type="unfinished">有効化</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation type="unfinished">無効化</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation type="unfinished">再起動するまで</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation type="unfinished">永久に</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation type="unfinished">許可</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation type="unfinished">拒否</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation type="unfinished">外部への接続</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation type="unfinished">プロセスの実行元:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation type="unfinished">次の実行ファイルを</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation type="unfinished">次のコマンドラインを</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation type="unfinished">&lt;b&gt;リモート&lt;/b&gt;プロセス %s は &lt;b&gt;%s&lt;/b&gt; で実行中です</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="unfinished">は &lt;b&gt;%s&lt;/b&gt; の %s ポート %d 番に接続しています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation type="unfinished">は&lt;b&gt;%s&lt;/b&gt; を %sの %s ポート %dで解決しようとしています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation type="unfinished">警告</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation type="unfinished">データベースの保存ファイルを選択するか、方式「メモリ内」を選択する必要があります。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation type="unfinished">データベース方式が変更されました</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation type="unfinished">GUIを再起動後反映されます</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation type="unfinished">サーバーアドレスは空白にすることはできません</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished">構成は反映されました。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation type="unfinished">&lt;b&gt;プロセス情報の読み込みでエラーが発生しました:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation type="unfinished">&lt;b&gt;プロセス監視の停止中にエラーが発生しました:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation type="unfinished">読み込み中...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation type="unfinished">接続しているノードがありません。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation type="unfinished">ルールが反映されました。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation type="unfinished">ルールの反映に失敗しました: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation type="unfinished">プロトコルを指定するかチェックを外してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation type="unfinished">プロトコルの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation type="unfinished">プロセスのパスを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation type="unfinished">プロセスパスの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation type="unfinished">コマンドラインを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation type="unfinished">コマンドラインの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation type="unfinished">宛先ポートを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation type="unfinished">宛先ポートの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation type="unfinished">宛先ホストを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation type="unfinished">宛先ホストの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation type="unfinished">宛先IP/ネットワークを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation type="unfinished">宛先IPの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation type="unfinished">ユーザーIDを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation type="unfinished">ユーザーIDの正規表現記法が誤っています</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation type="unfinished">リスト項目を指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation type="unfinished">リスト項目には必ずディレクトリを指定してください</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation type="unfinished">&lt;b&gt;ルールをサポートしていません&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation type="unfinished">&lt;b&gt;ルールの読み込みに失敗しました&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">名前</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">アドレス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">状態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">ホスト名</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">バージョン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">アクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">有効</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation type="unfinished">回数</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">プロトコル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation type="unfinished">停止中</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation type="unfinished">無効</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation type="unfinished">実行中</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation type="unfinished">OpenSnitch ネットワークモニター {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation type="unfinished">無効化</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation type="unfinished">有効化</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation type="unfinished">複製</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation type="unfinished">編集</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation type="unfinished">削除</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="unfinished">    このルールを消去しようとしています。    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation type="unfinished">    宜しいですか?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation type="unfinished">該当するルールが見つかりませんでした</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation type="unfinished">CSVファイルに保存</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished">&lt;b&gt;エラーr:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation type="unfinished">警告:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation type="unfinished">許可</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation type="unfinished">拒否</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation type="unfinished">常に</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation type="unfinished">再起動するまで</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation type="unfinished">    このルールを削除しようとしています。    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">名前</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">名前</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">アドレス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">状態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ホスト名</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">バージョン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">アクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">有効</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">回数</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">プロトコル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">名前</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">アドレス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">状態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">ホスト名</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">バージョン</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">アクション</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">有効期間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">ノード</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">有効</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">回数</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">プロトコル</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">プロセス</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">宛先</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">ルール</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">ユーザーID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">最終接続</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">引数</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">宛先IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">宛先ホスト</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">宛先ポート</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished">WARNING</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts b/ui/i18n/locales/lt_LT/opensnitch-lt_LT.ts
new file mode 100644 (file)
index 0000000..1824111
--- /dev/null
@@ -0,0 +1,3324 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="lt">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>Vartotojo ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Įvykdyta iš&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>TekstoEtiketė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Šaltinio IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>Proceso ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Paskirties IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Paskirties prievadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>iš šio vykdomojo failo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>iš šios komandinės eilutės</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>šis paskirties prievadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>šis vartotojas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>šis paskirties ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>kartą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30 sek.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1 val.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>amžinai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Drausti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Leisti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>iki perkrovimo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished">Įjungti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished">Išrinti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished">Išsaugoti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Nuostatos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Vartotojo sąsaja</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Numatytasis laiko limitas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Iššokančiojo lango numatytoji trukmė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Numatytoji trukmė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Numatytasis tikslas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>centre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>viršuje dešinėje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>apačioje dešinėje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>viršuje kairėje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>apačioje kairėje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>pagal vykdomąjį failą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>pagal komandinę eilutę</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>pagal paskirties prievadą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>pagal paskirties ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>pagal vartotojo ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>kartą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30 sek.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1 val.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>amžinai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>drausti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>leisti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Išjungti iššokančius langus, rodyti tik įspėjimą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Mazgai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Proceso stebėjimo metodas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Numatytoji trukmė bus taikoma, kai nėra prijungtos vartotojo sąsajos.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Mazgo adresas &lt;/p&gt;&lt;p&gt;Numatytoji reikšmė: unix:///tmp/osui.sock (unix:// privaloma, jei tai Unix lizdas) &lt;/p&gt;&lt;p&gt;Tai taip pat gali būti IP adresas su prievadu: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adresas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Numatytasis registravimo žurnale lygis</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Versija</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Numatytasis veiksmas bus atliekamas, kai nėra prijungtos vartotojo sąsajos.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Žurnalo failas, į kurį įrašomi žurnalo įrašai.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout spausdins žurnalus į standartinę išvestį.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Žurnalo failas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Kompiuterio vardas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>iki perkrovimo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>visada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Taikyti konfigūraciją visiems mazgams</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Duomenų bazė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>Atmintyje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Failas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Uždaryti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Taikyti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Išsaugoti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>iki perkrovimo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Duomenų bazės tipas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Pasirinkti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Rodyti išplėstinį rodinį automatiškai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Veiksmas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Jei pažymėta, iššokantys langai bus rodomi su aktyviu išplėstiniu rodiniu.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Trukmė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Taip pat filtruoti prisijungimus pagal:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>Vartotojo ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Paskirties prievadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Paskirties IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Šis laiko limitas yra atgalinis laikmatis, kurį matote, kai rodomas iššokantis dialogo langas.&lt;/p&gt;&lt;p&gt;Jei į iššokantį dialogo langą neatsakoma, taikomos numatytosios parinktys.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>Išplėstinis vaizdas leidžia lengvai pasirinkti kelis laukus, kad būtų galima filtruoti prisijungimus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Jei pažymėta, šis laukas bus pasirinktas, kai bus rodomas iššokantis langas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Iššokančio lango numatytasis veiksmas.&lt;/p&gt;&lt;p&gt;Kai ruošiamasi užmegzti naują išeinantį ryšį, šis veiksmas bus pasirinktas pagal numatytuosius nustatymus, todėl, jei suveiks laiko limitas, bus taikoma ši parinktis.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Kai iššokančiame lange prašoma leisti arba neleisti prisijungti: &lt;/p&gt;&lt;p&gt;1. Nauji išeinantys ryšiai neleidžiami.&lt;/p&gt;&lt;p&gt;2. Žinomi ryšiai leidžiami arba neleidžiami pagal vartotojo nustatytas taisykles.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Default action when the GUI is disconnected</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Debug invalid connections</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Iššokantys langai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Numatytosios parinktys</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Numatytoji padėtis ekrane</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>bet kokios laikinos taisyklės</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="478"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="481"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Don't save rules of duration</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Laikas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Paskirties vieta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protokolas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Procesas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Taisyklė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Mazgas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Events tab columns</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Proceso informacija</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>įkeliama…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: loading...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>mem stats: loading...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Būsena</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Atidaryti failus</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O Statistics</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Memory mapped files</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Stack</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Aplinkos kintamieji</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Application pids</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Pradėti arba sustabdyti šio proceso stebėjimą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Uždaryti</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Taisyklė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Mazgas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Taikyti taisyklę visiems mazgams</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Iš šios komandinės eilutės</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Iš šio vykdomojo failo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Veiksmas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>Į šį IP / tinklą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>kartą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="102"/>
+        <source>30s</source>
+        <translation type="obsolete">30 sek.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="107"/>
+        <source>5m</source>
+        <translation type="obsolete">5 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="112"/>
+        <source>15m</source>
+        <translation type="obsolete">15 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="117"/>
+        <source>30m</source>
+        <translation type="obsolete">30 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="122"/>
+        <source>1h</source>
+        <translation type="obsolete">1 val.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>visada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>Į šį prievadą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Iš šio vartotojo ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Kelių domenų nurodyti kableliais ar tarpais neleidžiama.
+
+Vietoj jų naudokite reguliariąsias išraiškas:
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+arba vieną domeną:
+www.gnu.org - atitiks tik www.gnu.org, nei ftp.gnu.org, nei www2.gnu.org, ...
+gnu.org - atitiks tik gnu.org, www.gnu.org, ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domenas.org, .*\.domenas.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Leidžiama naudoti tik TCP, UDP arba UDPLITE&lt;/p&gt;&lt;p&gt;Galite naudoti regexp, t. y.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="411"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Galite nurodyti vieną IP adresą:
+- 192.168.1.1
+
+arba reguliarią išraišką:
+- 192\.168\.1\.[0-9]+
+
+kelių IP adresų:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Taip pat galite nurodyti potinklį:
+- 192.168.1.0/24
+
+Pastaba: kableliais ar tarpais atskirti IP ar tinklų negalima.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>LAN</source>
+        <translation type="obsolete">LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="537"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="547"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="552"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="562"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="567"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="572"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="577"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="582"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="587"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Trukmė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protokolas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>To this host</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Drausti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Leisti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Pavadinimas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Įjungti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Taisyklės tikrinamos abėcėlės tvarka, todėl galite jas atitinkamai pavadinę nustatyti jų prioritetus.
+
+000-allow-localhost
+001-deny-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="935"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">palikite tuščią, kad sukurtumėte automatiškai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Jei pažymėta, ši taisyklė bus viršesnė už kitas taisykles. Po šios taisyklės nebus tikrinamos jokios kitos taisyklės.
+
+Taisyklę turite pavadinti taip, kad ji būtų tikrinama pirma, nes taisyklės tikrinamos abėcėlės tvarka. Pavyzdžiui:
+
+[x] Prioritetas - 000-prioritetinė-taisyklė
+[ ] Prioritetas - 001-mažesnio prioriteto taisyklė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Prioritetinė taisyklė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pagal numatytuosius nustatymus taisyklių lauke neribojamos didžiosios raidės, t. y., jei procesas bando prisijungti prie gOOgle.CoM, o jūs turite taisyklę Deny .*google.com, prisijungimas bus užblokuotas.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Jei pažymėsite šį laukelį, turėsite nurodyti tikslų (domeną, vykdomąjį failą, komandinę eilutę), kurią norite filtruoti.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Case-sensitive</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>iki perkrovimo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Į šį domenų sąrašą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pasirinkite katalogą su blokuojamų arba leidžiamų domenų sąrašais.&lt;/p&gt;&lt;p&gt;Į tą katalogą įdėkite failus su bet kokiu plėtiniu, kuriuose yra domenų sąrašai.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Kiekvieno sąrašo įrašo formatas yra toks (pagrindinio kompiuterio formatas): &lt;/p&gt;&lt;p&gt;127.0.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;arba &lt;/p&gt;&lt;p&gt;0.0.0.0.0 www.domenas.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation type="unfinished">Programos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch tinklo statistika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="284"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Įrašyti į CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="294"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Sukurti naują taisyklę</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;pagrindinio kompiuterio vardas - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Statusas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Pradėti arba sustabdyti perėmimą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Įvykiai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filtras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Leisti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation type="unfinished">Atmesti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Pvz.: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Mazgai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Taisyklės</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>įjungti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Application rules</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Nuolatinė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Laikina</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(dukart spustelėkite, kad peržiūrėtumėte detalią informaciją apie elementą)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Programos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adresai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Prievadai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Vartotojai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Connections</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Dropped</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Veikimo laikas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Versija</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation type="unfinished">Ištrinti visus perimtus įvykius</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Redaguoti taisyklę</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Ištrinti taisyklę</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(dukart spustelėkite, kad peržiūrėtumėte detalią informaciją apie taisyklę)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Visos programos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statistika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Pagalba</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Uždaryti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Įjungti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Išjungti</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished">Konfigūracija pritaikyta.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished">Šaltinio IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished">Įspėjimas</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Leisti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Drausti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>amžinai</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Outgoing connection</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Procesas paleistas iš:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>iš šios komandinės eilutės</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>iš šio vykdomojo failo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>iki perkrovimo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>į prievadą {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>į {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>iš vartotojo {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>į {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>į *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">į *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Nuotolinis&lt;/b&gt; procesas %s veikia &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>jungiasi prie &lt;b&gt;%s&lt;/b&gt; per %s prievadą %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>bando išspręsti &lt;b&gt;%s&lt;/b&gt; per %s, %s prievadą %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Serverio adresas negali būti tuščias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Konfigūracija pritaikyta.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Išimtis išsaugant konfigūraciją: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Taikoma konfigūracija {0} ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Įkeliant {0} konfigūraciją įvyko klaida</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Taikant konfigūraciją įvyko klaida: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Įspėjimas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Turite pasirinkti duomenų bazės failą&lt;br&gt;arba pasirinkite tipą „Atmintyje“.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>DB tipas pakeistas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Restart the GUI in order effects to take effect</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Užveskite pelės žymeklį virš teksto, kad būtų rodoma pagalba&lt;br&gt;&lt;br&gt;Nepamirškite apsilankyti wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Klaida įkeliant proceso informaciją:&lt;/b&gt;&lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Sustabdant stebėjimo procesą įvyko klaida:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>įkeliama…</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Nėra prijungtų mazgų.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Taisyklė pritaikyta.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>protokolas negali būti tuščias arba panaikinkite jo žymėjimą</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation type="unfinished">Protokolo regexp klaida</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>proceso kelias negali būti tuščias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Process path regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>komandinė eilutė negali būti tuščia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Command line regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Paskirties prievadas negali būti tuščias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Dst port regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Dest host can not be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Dst host regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Dest IP/Network can not be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Dst IP regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Vartotojo ID negali būti tuščias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>User ID regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Klaida taikant taisyklę: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Sąrašų laukas negali būti tuščias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Lists field must be a directory</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Taisyklė nepalaikoma&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Klaida įkeliant taisyklę&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Neveikia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Išjungta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Veikia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Ketinate ištrinti šią taisyklę.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Ar esate tuo tikras?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch tinklo statistika {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>{0} OpenSnitch tinklo statistika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation type="unfinished">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Išsaugoti kaip CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Išrinti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Išjungti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Įjungti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Duplicate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Redaguoti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Taisyklė pagal šį pavadinimą ir mazgą nerasta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Klaida:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Įspėjimas:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Leisti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Drausti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Visada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Iki perkrovimo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Ketinate ištrinti šią taisyklę.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">Última Conexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Pavadinimas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adresas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Būsena</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versija</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Taisyklės</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Laikas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Veiksmas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Trukmė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Mazgas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Įjungta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hits</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protokolas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Procesas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Paskirties vieta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Taisyklė</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Vartotojo ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>PaskutinisPrisijungimas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Args</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>PaskIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstHost</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>PaskPrievadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Veikimo laikas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Connections</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Dropped</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts b/ui/i18n/locales/nb_NO/opensnitch-nb_NO.ts
new file mode 100644 (file)
index 0000000..9773741
--- /dev/null
@@ -0,0 +1,3236 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nb_NO">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>Bruker-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Kjørt fra&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>TextLabel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Kilde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>Prosess-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Mål-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Målport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>fra denne kjørbare fil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>fra denne kommandolinje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>denne målport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>denne bruker</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>denne mål-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>en gang</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>for alltid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Nekt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Tillat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>til omstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>fra denne PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>handling</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1t</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Brannmur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Brannmur&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>Inngående</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>Utgående</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Profil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation>Ny regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation>Lukk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Brannmurregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished">Aktiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished">Beskrivelse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation>Retning</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation>INN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation>UT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation>Handling</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation>DROPP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation>AVVIS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation>Slett</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished">Lagre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation>Legg til</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Innstillinger</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Grensesnitt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Forvalgt utløpstid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Forvalgt varighet for sprettoppvindu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Forvalgt varighet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Forvalgt mål</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>midtstilt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>øvre høyre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>nedre høyre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>øvre venstre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>nedre venstre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>etter kjørbar fil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>etter kommandolinje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>etter målport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>etter mål-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>etter bruker-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>en gang</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>for alltid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>nekt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>tillat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Deshabilitar ventanas emergentes,
+sólo mostrar alerta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Noder</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Prosessovervåkingsmetode</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Forvalgt varighet tar effekt når det ikke er noe brukergrensesnitt tilkoblet.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Adressen til noden.&lt;/p&gt;&lt;p&gt;Forvalgt: unix:///tmp/osui.sock (unix:// er påkrevd hvis det er en Unix-socket)&lt;/p&gt;&lt;p&gt;Det kan også være en IP-adresse med port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Forvalgt loggnivå</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Versjon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Forvalgt handling når det ikke er noe brukergrensesnitt tilkoblet.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Loggfiler å skrive logger til.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout skriver logger til standard-ut.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Loggfil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>HostName</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>frem til omstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>alltid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Anvend oppsett for alle noder</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Database</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>I minnet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Fil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Lukk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Anvend</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Lagre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>frem til omstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Databasetype</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Velg</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Vis avansert fremvisning som forvalg</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Handling</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Varighet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Filtrer forbindelser også etter:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>Bruker-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Målport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Mål-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Forvalgt handling når det grafiske brukergrensesnittet er frakoblet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Feilsøk ugyldige forbindelser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Forvalgte innstillinger</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Forvalgt posisjon på skjermen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>alle midertidige regler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Tid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Mål</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Prosess</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Si se selecciona opensnitch te preguntará para permitir o denegar conexiones que no tienen un PID asociado. Esto puede pasar por diferentes motivos, principalmente debido a conexiones inválidas.&lt;/p&gt;&lt;p&gt;La ventana emergente sólo contendrá información de la conexión.&lt;/p&gt;&lt;p&gt;Hay algunas situaciones en las que estas conexiones son válidas, por ejemplo al establecer un túnel VPN con wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1t</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation>Regler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation>1t eller mindre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Fra denne kommandolinjen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Fra denne kjørbare filen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/ruta/al/ejecutable, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation type="unfinished">en gang</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Fra denne bruker-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Nekt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Tillat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation type="unfinished">Aktiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Prioritetsregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con listas de dominios para permitir o denegar.&lt;/p&gt;&lt;p&gt;Mete dentro de este directorio ficheros con cualquier extensión que contengan listas de dominios.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;El formato de cada dominio de la lista tiene que estar en formato hosts, así:&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;o &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will only match the executable path. It is not modifiable by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;You can use regular expressions to deny executions from /tmp for example:&lt;br/&gt;&lt;/p&gt;&lt;p&gt;^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este campo sólo comprueba la ruta del ejecutable (la cual no es modificable por el usuario).&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Puedes usar expresiones regulares para denegar cualquier ejecución desde /tmp, por ejemplo; ^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>Fra denne PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Nettverk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation>Liste med domener/IP-nummer</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation>Til denne listen med nettverkområder</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation>Til denne listen med IP-adresser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>Til denne listen med domener 
+(regulæruttrykk)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Avvis</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="937"/>
+        <source>Color</source>
+        <translation type="obsolete">Farge</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch nettverkstatistikk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Lagre til CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Lag ny regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;vertsnavn - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Hendelser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Tillat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Nekt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>F.eks.: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Noder</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Dirección para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Regler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>aktiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">buscar regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Midlertidig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Verter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en un dominio para ver detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adresser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Porter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Brukere</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Forbindelser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Droppet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Oppetid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Versjon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Endre regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Slett regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Borrar todos los hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Borrar todos las aplicaciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Borrar todas las direcciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Borrar todos los puertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Borrar todos los usuarios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(Doble click en una fila para editar una regla)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Borrar conexiones que coinciden con esta regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Avvis</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished">2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statistikk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Hjelp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Lukk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Aktiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Deaktiver</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation>Feil: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation>Tar i bruk endringer...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation>Aktiverer brannmur...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation>Deaktiverer brannmur...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Målport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Kildeport</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>Mål-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished">Kilde-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation>Brannmurregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished">Aviso</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Tillat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Nekt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>for alltid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Utgående forbindelse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Prosess startet fra:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>fra denne kommandolinjen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation type="unfinished">fra denne kjørbare fil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>frem til omstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>til port {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>til {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>fra bruker {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>til {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>til *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">a *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>oppretter forbindelse til &lt;b&gt;%s&lt;/b&gt; på %s port %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished">fra denne PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished">Rechazar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>oppretter forbindelse til &lt;b&gt;%s&lt;/b&gt;, %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Feil med målvert-regulæruttrykk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Mål-IP/nettverk kan ikke være blank</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Feil med regulæruttrykk for mål-IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Bruker-ID kan ikke være blank</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Feil med regulæruttrykk for bruker-ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Feil ved anvending av regel: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Regelen støttes ikke&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Feil ved regellasting&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Det er allerede en regel med dette navnet.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>PID-feltet kan ikke være blankt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>Feil med regulæruttrykk for PID-felt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>Velg minst ett felt.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Kjører ikke</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Deaktivert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Kjører</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Du er i ferd med å slette denne regelen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Er du sikker?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Treff</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Lagre som CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Slett</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Deaktiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Aktiver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Rediger</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Feil:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Advarsel:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Tillat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation type="unfinished">Nekt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Alltid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Du er i ferd med å slette denne regelen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="174"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Navn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adresse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Vertsnavn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versjon</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Handling</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Varighet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Aktivert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Treff</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protokoll</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Prosess</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Mål</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>BrukerID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>SisteForbindelse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Args</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>MålIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>MålVert</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>MålPort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="175"/>
+        <source>Addr</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Rechazadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Hva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Anvend på</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Avvis</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Nettverknavn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="378"/>
+        <source>Addr</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Oppetid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Forbindelser</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Droppet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hva</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Prioritet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Koblet opp ny node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Beskrivelse</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kommandolinje</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation>Eksporter regler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation>Importer regler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation>Eksporter hendelser til CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation>Avslutt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation>Eksporter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation>Til utklippstavle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation>Til disk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/nl_NL/opensnitch-nl_NL.ts b/ui/i18n/locales/nl_NL/opensnitch-nl_NL.ts
new file mode 100644 (file)
index 0000000..b0175ad
--- /dev/null
@@ -0,0 +1,3310 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="nl">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>Gebruikers-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Wordt uitgevoerd op&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>TekstLabel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Bron-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>Proces-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Bestemmings-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>dit uitvoerbare bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>deze opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>deze bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>deze gebruiker</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>deze bestemmings-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>eenmalig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>oneindig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Weigeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Toestaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>tot herstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>deze PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>actie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30 sec.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1 uur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Firewall</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>Inkomend</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>Uitgaand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Profiel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>Sta inkomende verbindingen op een bepaalde poort toe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>Dienst toestaan (IN)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>Weiger uitgaande verbindingen op een bepaalde poort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation>Dienst toestaan (UIT)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation>Nieuwe regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="453"/>
+        <source>xxx</source>
+        <translation type="obsolete">xxx</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation>Sluiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Firewallregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Knooppunt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="34"/>
+        <source>All</source>
+        <translation type="obsolete">Alles</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>Inschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>Beschrijving</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>Eenvoudig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>Voorwaarde toevoegen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>Voorwaarde verwijderen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation>Richting</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation>IN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation>UIT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation>Actie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation>TOESTAAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation>AFWIJZEN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation>WEIGEREN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation>TERUGSTUREN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation>Wissen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation>Verwijderen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation>Opslaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation>Toevoegen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Instellingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Vormgeving</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Stadaard time-out</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>De standaard meldingsduur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Standaardduur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Standaarddoel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>Midden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>Rechtsboven</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>Rechtsonder</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>Linksboven</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>Linksonder</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>Uitvoerbaar bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>Opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>Bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>Bestemmings-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>Gebruikers-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>Eenmalig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>Oneindig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>Weigeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>Toestaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Deshabilitar ventanas emergentes,
+sólo mostrar alerta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Knooppunten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Procesmonitormethode</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De standaardduur die wordt gebruikt als er geen grafisch programma is.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Het adres van het knooppunt.&lt;/p&gt;&lt;p&gt;Standaard: unix:///tmp/osui.sock (unix:// is vereist bij gebruik van een Unix-socket)&lt;/p&gt;&lt;p&gt;Dit kan ook een ip-adres met poort zijn: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Standaard logniveau</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Versie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De standaardactie die wordt uitgevoerd als er geen grafisch programma is.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Het logboek waarin logregels worden genoteerd.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout voegt regels toe aan de standaarduitvoer.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Logboek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Hostnaam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>Tot herstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>Altijd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Instellingen toepassen op alle knooppunten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Databank</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>In geheugen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Sluiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Toepassen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Opslaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>Tot herstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Soort databank</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Kiezen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Altijd uitgebreide meldingen tonen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Actie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kruis aan om uitgebreide meldingen te tonen.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Duur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Als een eenvoudige melding wordt getoond, dan kunt u verbindingen of programma's filteren op basis van een bepaalde eigenschap (uitvoerbaar bestand, poort, ip-adres, etc.).&lt;/p&gt;&lt;p&gt;Met deze opties heeft u keuze uit meerdere mogelijkheden om verbindingen te filteren.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Verbindingen tevens filteren op:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>Gebruikers-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Bestemmings-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Deze time-out telt af tot het moment waarop u een melding te zien krijgt.&lt;/p&gt;&lt;p&gt;Als er geen keuze wordt gemaakt, dan worden de standaardopties gebruikt.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>In de uitgebreide weergave kunt u eenvoudig meerdere keuzes maken om verbindingen te filteren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Kruis aan om dit veld te kiezen als er een melding wordt getoond</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De standaard meldingsactie.&lt;/p&gt;&lt;p&gt;Als er een nieuwe uitgaande verbinding wordt opgezet, dan wordt standaard deze actie uitgevoerd als de time-out optreedt.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Als een melding vraagt om een verbinding toe te staan of te weigeren:&lt;/p&gt;&lt;p&gt;1. nieuwe uitgaande verbindingen worden geweigerd;&lt;/p&gt;&lt;p&gt;2. bekende verbindingen worden toegestaan of geweigerd op basis van zelf-opgegeven regels.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Standaardactie bij geen grafisch programma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Fouten van slechte verbindingen opsporen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Meldingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Standaardopties</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Standaard meldingspositie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>Iedere tijdelijke regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Cuando esta opción está seleccionada, las reglas de la duración elegida no se añadirán a la lista de reglas temporales en la GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Las reglas temporales seguirán siendo válidas, y puedes usarlas cuando se pregunte para permitir o denegar una nueva conexión.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">No guardar reglas de duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Tijdstip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Bestemming</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Knooppunt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Si se selecciona opensnitch te preguntará para permitir o denegar conexiones que no tienen un PID asociado. Esto puede pasar por diferentes motivos, principalmente debido a conexiones inválidas.&lt;/p&gt;&lt;p&gt;La ventana emergente sólo contendrá información de la conexión.&lt;/p&gt;&lt;p&gt;Hay algunas situaciones en las que estas conexiones son válidas, por ejemplo al establecer un túnel VPN con wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Gebeurteniskolommen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Interactieve meldingen uitschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Meldingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Systeemmeldingen gebruiken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Qt-meldingen gebruiken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation>Uitproberen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kruis aan om OpenSnitch te laten vragen of u verbindingen zonder PID wilt toestaan of weigeren. Dit kan bijvoorbeeld handig zijn bij verbindingen met een slechte status.&lt;/p&gt;&lt;p&gt;Het pop-upvenster bevat alleen informatie over de networkverbinding.&lt;/p&gt;&lt;p&gt;In sommige gevallen zijn deze verbindingen echter niet slecht, bijvoorbeeld bij het opzetten van een vpn met behulp van WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>minuten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation>Aantal minuten tussen gebeurtenisverwijderingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>dagen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Aantal te behouden gebeurtenisdagen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>Afwijzen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation>Systeem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation>Opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Thema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30 sec.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30 min.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1 uur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation>Regels</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation>Kruis aan om de regels van de gekozen duur niet toe te voegen aan de lijst met tijdelijke regels.
+
+De tijdelijke regels blijven echter geldig en kunnen worden gebruikt indien om een actie gevraagd wordt.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>Regelduur niet onthouden/verwijderen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation>30 sec. of korter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation>5 min. of korter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation>15 min. of korter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation>30 min. of korter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation>1 uur of korter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Procesinformatie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>Bezig met laden…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: bezig met laden…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>Geheugenstatistieken: bezig met laden…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Geopende bestanden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O-statistieken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Bestanden in geheugen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Stapel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Omgevingsvariabelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Programma-pid's</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Procesmonitoring starten/stoppen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Sluiten</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Knooppunt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Regel toepassen op alle knooppunten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Van deze opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Van dit uitvoerbare bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Actie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/ruta/al/ejecutable, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>Naar dit ip-adres/netwerk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>Eenmalig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>Altijd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>Naar deze poort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Van deze gebruikers-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Komma's of spaties om meerdere domeinnamen op te geven zijn niet toegestaan. 
+
+Gebruik in plaats daarvan reguliere uitdrukking: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+of een losse domeinnaam:
+www.gnu.org - komt alleen overeen met www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - komt alleen overeen met gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domein.org, .*\.domein.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Alleen tcp, udp en udplite toegestaan&lt;/p&gt;&lt;p&gt;U kunt reguliere uitdrukkingen gebruiken, zoals ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>Tcp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>U kunt een los ip-adres opgeven:
+- 192.168.1.1
+
+of een reguliere uitdrukking:
+- 192\.168\.1\.[0-9]+
+
+meerdere ip-adressen:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Een subnet is ook mogelijk:
+- 192.168.1.0/24
+
+Let op: komma's en spaties om adressen en netwerken te scheiden zijn niet toegestaan.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Duur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>Naar deze host</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Weigeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Toestaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Inschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>De regels worden op alfabetische volgorde uitgevoerd, dus geef ze een naam die prioriteit aanduidt.
+
+000-allow-localhost
+001-deny-broadcast
+…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">dejar en blanco para autoasignar nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Kruis aan om deze regel boven de rest te laten gaan. Na deze regels worden geen nieuwe meer aangekruist.
+
+Let op: regels worden in alfabetische volgorde uitgevoerd. Voorbeeld:
+
+[x] Prioriteit - 000-priority-rule
+[  ] Prioriteit - 001-less-priority-rule</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Prioriteitsregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Standaard zijn regels hoofdlettergevoelig. Als een proces dus verbinding wil maken met gOOgle.CoM en u een regel hebt opgegeven om .*google.com te weigeren, dan wordt de verbinding geblokkeerd.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Als u deze optie aankruist, dan dient u de exacte tekenreeks (domeinnaam, uitvoerbaar bestand, opdrachtregel) die u wilt filteren op te geven.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Hoofdlettergevoelig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;U kunt met behulp van reguliere uitdrukkingen meerdere poorten opgeven:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 of 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 of 5551, 5552, 5553, etc.:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>Tot herstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Naar deze lijst met domeinnamen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecciona un directorio con listas de dominios para permitir o denegar.&lt;/p&gt;&lt;p&gt;Mete dentro de este directorio ficheros con cualquier extensión que contengan listas de dominios.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;El formato de cada dominio de la lista tiene que estar en formato hosts, así:&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;o &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Programma's</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will only match the executable path. It is not modifiable by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;You can use regular expressions to deny executions from /tmp for example:&lt;br/&gt;&lt;/p&gt;&lt;p&gt;^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este campo sólo comprueba la ruta del ejecutable (la cual no es modificable por el usuario).&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Puedes usar expresiones regulares para denegar cualquier ejecución desde /tmp, por ejemplo; ^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dit veld bevat de door de gebruiker uitgevoerde opdrachtregel.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Als de gebruiker een opdracht invoert, dan wordt alleen de opdracht getoond:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Als de gebruiker een directe of relatieve opdrachtlocatie opgeeft, dan wordt het volgende getoond:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>Van deze PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Netwerk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation>Lijst met domeinen/ip's</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation>Naar deze lijst met netwerkreeksen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation>Naar deze lijst met ip-adressen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kies een map met bestanden die een lijst met toe te stane of te weigeren ip-adressen bevat:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;Eén ip-adres per regel. Blanco regels of regels beginnend met # worden genegeerd.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kies een map met bestanden die een lijst met toe te stane of te weigeren netwerkreeksen bevat:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;/p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Eén reeks per regel. Blanco regels of regels beginnend met # worden genegeerd.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kies een map met lijsten met toe te stane of te weigeren domeinnamen.&lt;/p&gt;&lt;p&gt;Voorzie de map van bestanden met welke extensie dan ook.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;De opmaak van elke regel is als volgt (hostsopmaak):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domein.nl&lt;/p&gt;&lt;p&gt;of &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domein.nl&lt;/p&gt;&lt;p&gt;Blanco regels of regels beginnend met # worden genegeerd.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>Naar deze lijst met domeinnamen
+(reguliere uitdrukkingen)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Kies een map met reguliere-uitdrukkingsbestanden met toe te stane of te weigeren domeinnamen.&lt;/p&gt;&lt;p&gt;.*\.voorbeeld\.nl.&lt;/p&gt;&lt;p&gt;U kunt ook een volledige domeinnaam gebruiken: &amp;quot;voorbeeld.nl&amp;quot; , die vervolgens overeenkomt met iets.voorbeeld.nl, iets.voorbeeld.nl.localdomain, etc.&lt;/p&gt;&lt;p&gt;Eén domeinnaam per regel. Blanco regels of regels beginnend met # worden genegeerd.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Afwijzen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation>Beschrijving…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;De waarde van dit veld is altijd de directe locatie naar het uitvoerbare bestand: /locatie/van/bestand&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Voorbeelden:&lt;/p&gt;&lt;p&gt;- Eenvoudig: /locatie/van/bestand&lt;/p&gt;&lt;p&gt;- Meerdere locaties: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Meerdere bestanden: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Weiger-/Toestaanacties met behulp van /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Bekijk meer voorbeelden op onze &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wikipagina&lt;/a&gt; of vraag hulp ons &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;forum&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>Is een reguliere uitdrukking</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation>Is een reguliere uitdrukking</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation>Netwerkinterface</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation>Overig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>Leg met deze regel overeenkomende verbindingen niet vast in het logboek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation>Verbindingen niet vastleggen in logboek</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Weigeren negeert de verbinding</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Afwijzen wijst de verbinding af en sluit de socket in kwestie af</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>Toestaan staat de verbinding toe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="83"/>
+        <source>Name (leave blank to autocreate)</source>
+        <translation type="obsolete">Naam (laat leeg om automatisch aan te maken)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation>Icmp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation>Icmp6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation>Sctp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation>Sctp6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="937"/>
+        <source>Color</source>
+        <translation type="obsolete">Kleur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch-netwerkstatistieken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Exportar a CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Nieuwe regel opstellen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;Hostnaam - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Onderscheppen aan/uit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Gebeurtenissen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Toestaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Weigeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Bijv. firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Knooppunten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Dirección para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Regels</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>Inschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">buscar regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Programmaregels</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Permanent</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Tijdelijk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en un dominio para ver detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Programma's</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adressen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Poorten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Gebruikers</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Verbindingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Afgewezen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Uptime</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Versie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Alle onderschepte gebeurtenissen wissen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Regel bewerken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Regel verwijderen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Borrar todos los hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Borrar todos las aplicaciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Borrar todas las direcciones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Borrar todos los puertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Borrar todos los usuarios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(Doble click en una fila para editar una regla)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="912"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Borrar conexiones que coinciden con esta regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Alle programma's</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Afwijzen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation>Systeemregels</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation>Knooppunt verwijderen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation>Knooppuntvoorkeuren openen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation>Onderscheppen van knooppunt aan/uit</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statistieken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Hulp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Sluiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Inschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Uitschakelen</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation>De instellingen zijn toegepast.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation>Foutmelding: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation>Bezig met toepassen van wijzigingen…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>Foutmelding tijdens opvragen van INVOERbeleid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>Foutmelding tijdens opvragen van UITVOERbeleid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>De firewallregels kunnen in het grafische programma alléén worden ingesteld met ‘nftables’ en niet met ‘iptables’</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation>Bezig met inschakelen van firewall…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation>Bezig met uitschakelen van firewall…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Bronpoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>Bestemmings-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>Bron-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>Invoerinterface</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>Uitvoerinterface</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>Conntrackmarkering instellen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>Conntrackmarkering overeen laten komen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>Conntrackstatus(sen) overeen laten komen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>Pakket markeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>Pakketinformatie markeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>Bandbreedtequota</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>Verbindingen met beperkt gebruik</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>Uw protobuf-version is incompatibel. Installeer protobuf 3.8.0 of nieuwer
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation>De regel is verwijderd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation>De regel is toegevoegd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation>U kunt gebruikmaken van ‘,’ of ‘-’ om meerdere poorten/ip-adressen of reeksen/waarden op te geven:&lt;br&gt;&lt;br&gt;Poorten: 22 of 22,443 of 50000-60000&lt;br&gt;Ip-adressen: 192.168.1.1 of 192.168.1.30-192.168.1.130&lt;br&gt;Waarden: echo-reply,echo-request&lt;br&gt;Waarden: new,established,related</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation>Bezig met verwijderen van regel…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation>De regel kan niet worden bijgewerkt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation>Bezig met toevoegen van regel…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation>&lt;Kies een uitdrukking&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation>Gelijk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation>Niet gelijk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation>Groter dan of gelijk aan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation>Groter dan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation>Kleiner dan of gelijk aan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation>Kleiner dan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation>Firewallregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation>Eenvoudig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation>Uitgebreid</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation>Deze regel wordt nog niet ondersteund.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation>Dienst uitsluiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>Sta inkomende verbindingen toe op de gekozen poort.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>Sta uitgaande verbindingen toe op de gekozen poort.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation>Kies een uitdrukking.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>De waarde mag niet 0 of blanco zijn.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>De waarde-opmaak is 1024/kbytes (of bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>De waarde-opmaak is 1024/kbytes/second (of bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>De beperking is ongeldig - gebruik bytes, mbytes of gbytes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>De beperking is ongeldig - gebruik second, minute, hour of day</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation>De poort is ongeldig.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation>Informatie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation>Foutmelding</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation>Waarschuwing</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>Systeemmeldingen zijn niet beschikbaar - installeer python3-notify2.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Toestaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Weigeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>Oneindig</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Uitgaande verbinding</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Proces gestart via:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>deze opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>dit uitvoerbare bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>Tot herstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>naar poort {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>naar {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>naar gebruiker {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>naar {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>naar *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">a *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Extern&lt;/b&gt; proces %s actief op &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>maakt verbinding met &lt;b&gt;%s&lt;/b&gt; op %s poort %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>tracht &lt;b&gt;%s&lt;/b&gt; te herleiden via %s, %s poort %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>van deze PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation>Nieuwe uitgaande verbinding</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Afwijzen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>maakt verbinding met &lt;b&gt;%s&lt;/b&gt;, %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Het serveradres mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>De instellingen zijn toegepast.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Uitzondering tijdens opslaan van instellingen: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Bezig met toepassen van instellingen op {0}…</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Foutmelding tijdens laden van {0}-instellingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Foutmelding tijdens toepassen van instellingen: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Waarschuwing</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Kies een databankbestand&lt;br&gt;of ‘In geheugen’.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>Het soort DB is gewijzigd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Herstart het programma om de wijzigingen toe te passen.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Houd de cursor boven teksten om hulpballonnen te tonen&lt;br&gt;&lt;br&gt;Bekijk ook de wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation>Systeem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>Er zijn geen thema's beschikbaar. Installeer qt-material: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation>Het thema is gewijzigd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Herstart het programma om het nieuwe thema toe te passen.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation>Oké</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="472"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation type="obsolete">Herstart het programma om de wijzigingen toe te passen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>Er zijn geen knooppunten verbonden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>Uitzondering tijdens opslaan van knooppuntinstellingen {0}: {1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Foutmelding tijdens laden van procesinformatie:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Foutmelding tijdens stoppen van monitorproces:&lt;/b&gt; &lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>Bezig met laden…</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Er zijn geen knooppunten verbonden.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>De regel is toegepast.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>Het protocol mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>De proceslocatie mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in proceslocatie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>De opdrachtregel mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>De bestemmingspoort mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>De bestemmingspoort mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>De bestemmings-ip/-netwerk mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in bestemmings-ip/-netwerk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>De gebruikers-id mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in gebruikers-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>De regel kan niet worden toegepast: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Het lijstveld mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Het lijstveld dient een map te zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Deze regel wordt niet ondersteund&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;De regel kan niet worden geladen&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Er is al een regel met deze naam.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>Het PID-veld mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in PID-veld</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>Kies minimaal één veld.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation>De netwerkinterface mag niet blanco zijn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation>Reguliere-uitdrukkingsfout in netwerkinterface</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Inactief</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Uitgeschakeld</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Actief</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    U staat op het punt om deze regel te verwijderen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Weet u het zeker?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch-netwerkstatistieken {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch-netwerkstatistieken van {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="177"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Tikken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Opslaan als csv-bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Verwijderen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Uitschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Inschakelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Klonen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Bewerken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Er is geen regel met die naam en dat knooppunt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Foutmelding:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Waarschuwing:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Toestaan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Weigeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Altijd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Tot herstart</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    U staat op het punt om deze regel te verwijderen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="174"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Naam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Status</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hostnaam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regels</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tijdstip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Actie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Duur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Knooppunt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ingeschakeld</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tikken</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Bestemming</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Gebruikers-id</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>RecentsteVerbinding</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Args</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Bestemmings-ip</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Bestemmingshost</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Bestemmingspoort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="175"/>
+        <source>Addr</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Rechazadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Wat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Toepassen op</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Afwijzen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Netwerknaam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="378"/>
+        <source>Addr</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Uptime</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Verbindingen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Afgewezen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Wat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Voorkomen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Nieuw knooppunt verbonden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Beschrijving</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Opdrachtregel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation>Regels exporteren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation>Regels importeren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation>Gebeurtenissen exporteren naar csv-bestand</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation>Afsluiten</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation>Exporteren</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation>Naar klembord</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation>Naar schijf</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation>Kies een exportmap</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    U staat op het punt om dit item te verwijderen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    U staat op het punt om dit knooppunt te verwijderen.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Het knooppunt kan niet worden verwijderd&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation>De regels kunnen niet worden geëxporteerd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>Kies een map met te importeren regels (json-bestanden)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation>De regels zijn geïmporteerd</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts b/ui/i18n/locales/pt_BR/opensnitch-pt_BR.ts
new file mode 100644 (file)
index 0000000..7a7f907
--- /dev/null
@@ -0,0 +1,3393 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="pt_BR">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>ID do usuário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executado de&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>TextLabel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>IP de origem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>ID de processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>IP de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Porta Dst</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="226"/>
+        <source>(/path/to/bin/chromium)</source>
+        <translation type="obsolete">(/caminho/para/bin/chromium)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="271"/>
+        <source>Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</source>
+        <translation type="obsolete">O navegador da Web Chromium deseja se conectar a www.evilsocket.net na porta tcp 443. E talvez a www.goodsocket.net na porta 344</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>a partir deste executável</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>a partir desta linha de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>esta porta de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>este usuário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>este ip de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>uma vez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">para esta sessão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>para sempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Negar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>a partir desse PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Firewall</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>De entrada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>De saída</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Perfil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>Permitir conexões de entrada para uma porta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>Permitir serviço (Entrada)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="398"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>Excluir conexões de saída para uma porta de serem interceptadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="407"/>
+        <source>Allow service (OUT)</source>
+        <translation>Permitir serviço (Saída)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="427"/>
+        <source>New rule</source>
+        <translation>Nova regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="421"/>
+        <source>Close</source>
+        <translation>Fechar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Regra do firewall</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>Descrição</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>Simple</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>Adicionar nova condição</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>Remover condição selecionada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="221"/>
+        <source>Direction</source>
+        <translation>Direção</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="232"/>
+        <source>IN</source>
+        <translation>Entrada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="241"/>
+        <source>OUT</source>
+        <translation>Saída</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="268"/>
+        <source>Action</source>
+        <translation>Ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="279"/>
+        <source>ACCEPT</source>
+        <translation>ACEITAR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="288"/>
+        <source>DROP</source>
+        <translation>DERRUBAR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="297"/>
+        <source>REJECT</source>
+        <translation>REJEITAR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="306"/>
+        <source>RETURN</source>
+        <translation>RETORNAR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="432"/>
+        <source>Clear</source>
+        <translation>LImpar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="443"/>
+        <source>Delete</source>
+        <translation>Deletar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="454"/>
+        <source>Save</source>
+        <translation>Salvar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="465"/>
+        <source>Add</source>
+        <translation>Adicionar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="250"/>
+        <source>FORWARD</source>
+        <translation>AVANÇAR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="255"/>
+        <source>PREROUTING</source>
+        <translation>PRÉ-ENCAMINHAMENTO</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="260"/>
+        <source>POSTROUTING</source>
+        <translation>PÓS-ROTEAMENTO</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="315"/>
+        <source>QUEUE</source>
+        <translation>FILA</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="323"/>
+        <source>DNAT</source>
+        <translation>DNAT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="328"/>
+        <source>SNAT</source>
+        <translation>SNAT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="333"/>
+        <source>REDIRECT</source>
+        <translation>REDIRECIONAR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="349"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation>dependendo da Ação (ou seja: alvo), a sintaxe dos parâmetros irá variar.
+Alguns exemplos:
+
+FILA -&gt; num 0 (ou 1, 2, ...)
+REDIRECIONAMENTO, TPROXY, DNAT, SNAT, MASCARADO:
+ para :22
+ para 192.168.1.254:8080
+ para 192.168.1.254
+ para 1024-2048 (mascarado)</translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Preferências</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>UI</source>
+        <translation>UI</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este tempo limite é a contagem regressiva que você vê quando uma caixa de diálogo pop-up é exibida.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="469"/>
+        <source>Default timeout</source>
+        <translation>Tempo limite padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Duração padrão do pop-up</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1140"/>
+        <source>Default duration</source>
+        <translation>Duração padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Ação padrão de pop-up</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Ação padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Alvo padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>centro</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>superior direito</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>inferior direito</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>superior esquerdo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>inferior esquerdo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posição padrão da caixa de diálogo de prompt na tela</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>por executável</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>por linha de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>por porta de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>por ip de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>por id de usuário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1148"/>
+        <source>once</source>
+        <translation>uma vez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">para esta sessão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>para sempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1116"/>
+        <source>deny</source>
+        <translation>negar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1125"/>
+        <source>allow</source>
+        <translation>permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="411"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Desativar pop-ups, exibir apenas um alerta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="950"/>
+        <source>Nodes</source>
+        <translation>Nodes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1183"/>
+        <source>Process monitor method</source>
+        <translation>Método de monitoramento de processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1137"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A duração padrão ocorrerá quando não houver interface do usuário conectada.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1077"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Endereço do node&lt;/p&gt;&lt;p&gt;Padrão: unix:///tmp/osui.sock (unix:// é obrigatório se for um soquete Unix)&lt;/p&gt;&lt;p&gt;Também pode ser um endereço IP com a porta: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1080"/>
+        <source>Address</source>
+        <translation>Endereço</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1301"/>
+        <source>Default log level</source>
+        <translation>Nível de registro padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1010"/>
+        <source>Version</source>
+        <translation>Versão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1099"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A ação padrão ocorrerá quando não houver interface do usuário conectada.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="681"/>
+        <source>audit</source>
+        <translation type="obsolete">auditar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1234"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Arquivo de log para gravar logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout irá imprimir registros na saída padrão.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1237"/>
+        <source>Log file</source>
+        <translation>Arquivo de log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="788"/>
+        <source>IMPORTANT</source>
+        <translation type="obsolete">IMPORTANTE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="793"/>
+        <source>WARNING</source>
+        <translation type="obsolete">ADVERTÊNCIA</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="798"/>
+        <source>ERROR</source>
+        <translation type="obsolete">ERRO</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&amp;lt;html&amp;gt;&amp;lt;head/&amp;gt;&amp;lt;body&amp;gt;&amp;lt;p&amp;gt;Se marcado, o opensnitch solicitará que você permita ou negue conexões que não tenham um PID associado, devido a vários motivos.&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;A caixa de diálogo pop-up conterá apenas informações sobre a conexão de rede.&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexões desconhecidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="962"/>
+        <source>HostName</source>
+        <translation>HostName</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1091"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1153"/>
+        <source>until restart</source>
+        <translation>até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1158"/>
+        <source>always</source>
+        <translation>sempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1312"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1317"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1052"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Aplicar configuração a todos os nodes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1353"/>
+        <source>Database</source>
+        <translation>Base de dados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="630"/>
+        <source>Database name</source>
+        <translation type="obsolete">Nome do banco de dados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1388"/>
+        <source>In memory</source>
+        <translation>Na memória</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1393"/>
+        <source>File</source>
+        <translation>Arquivo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>/path/to/the/file.db</source>
+        <translation type="obsolete">/caminho/para/o/arquivo.db</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1696"/>
+        <source>Close</source>
+        <translation>Fechar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1707"/>
+        <source>Apply</source>
+        <translation>Aplicar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1718"/>
+        <source>Save</source>
+        <translation>Salvar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1407"/>
+        <source>Database type</source>
+        <translation>Tipo de banco de dados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1414"/>
+        <source>Select</source>
+        <translation>Selecionar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Configure the</source>
+        <translation type="obsolete">Configure o</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opções padrão de pop-ups</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posição padrão dos pop-ups na tela</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="105"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;A visualização avançada permite que você aplique mais filtros em uma conexão&lt;/p&gt;&lt;p&gt;quando um pop-up aparece.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Mostrar visualização avançada por padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="815"/>
+        <source>Action</source>
+        <translation>Ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Se marcado, os pop-ups serão exibidos com a visualização avançada ativa.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Por padrão, quando um novo pop-up aparece, em sua forma mais simples, você será capaz de filtrar conexões ou aplicativos por uma propriedade da conexão (executável, porta, IP, etc).&lt;/p&gt;&lt;p&gt;Com essas opções, você pode escolher vários campos para filtrar conexões para.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Filtre as conexões também por:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="365"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Se marcado, este campo será verificado quando um pop-up for exibido</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>ID do usuário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Porta de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>IP de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este tempo limite é a contagem regressiva que você vê quando uma caixa de diálogo pop-up é exibida.&lt;/p&gt;&lt;p&gt;Se o pop-up não for respondido, as opções padrão serão aplicadas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>A visualização avançada permite que você selecione facilmente vários campos para filtrar conexões</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Se marcado, este campo será selecionado quando um pop-up for exibido</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Ação padrão de pop-up.&lt;/p&gt;&lt;p&gt;Quando uma nova conexão de saída está prestes a ser estabelecida, esta ação será selecionada por padrão, então se o tempo limite disparar, esta é a opção que será aplicada.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Enquanto um pop-up pede ao usuário para permitir ou negar uma conexão:&lt;/p&gt;&lt;p&gt;1. novas conexões de saída são negadas.&lt;/p&gt;&lt;p&gt;2. conexões conhecidas são permitidas ou negadas com base nas regras definidas pelo usuário.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Ação padrão quando a GUI é desconectada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1169"/>
+        <source>Debug invalid connections</source>
+        <translation>Depurar conexões inválidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Pop-ups</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Opções padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Posição padrão na tela</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="896"/>
+        <source>any temporary rules</source>
+        <translation>quaisquer regras temporárias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Quando esta opção é selecionada, as regras da duração selecionada não serão adicionadas à lista de regras temporárias na GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;As regras temporárias ainda serão válidas e você pode usá-las quando solicitado a permitir/negar uma nova conexão.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="490"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Não salve regras de duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="751"/>
+        <source>Time</source>
+        <translation>Tempo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="831"/>
+        <source>Destination</source>
+        <translation>Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>Protocol</source>
+        <translation>Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="847"/>
+        <source>Process</source>
+        <translation>Processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="767"/>
+        <source>Rule</source>
+        <translation>Regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="783"/>
+        <source>Node</source>
+        <translation>Node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&amp;lt;html&amp;gt;&amp;lt;head/&amp;gt;&amp;lt;body&amp;gt;&amp;lt;p&amp;gt;Se marcado, o opensnitch solicitará que você permita ou negue conexões que não tenham um PID asocciado, devido a vários motivos, principalmente devido a conexões de mau estado.&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;A caixa de diálogo pop-up conterá apenas informações sobre a conexão de rede.&amp;lt;/p&amp;gt;&amp;lt;p&amp;gt;Existem alguns cenários em que essas conexões são válidas, como ao estabelecer uma VPN usando wireguard.&amp;lt;/p&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="712"/>
+        <source>Events tab columns</source>
+        <translation>Colunas da guia de eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>por PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1166"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Se marcado, o OpenSnitch solicitará que você permita ou negue conexões que não tenham um PID associado, devido a vários motivos, principalmente devido a conexões ruins.&lt;/p&gt;&lt;p&gt;A caixa de diálogo pop-up conterá apenas informações sobre a conexão de rede.&lt;/p&gt;&lt;p&gt;Existem alguns cenários em que essas conexões são válidas, como ao estabelecer uma VPN usando o WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="476"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Desativar pop-ups, exibir apenas uma notificação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="616"/>
+        <source>Desktop notifications</source>
+        <translation>Notificações da área de trabalho</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="646"/>
+        <source>Use system notifications</source>
+        <translation>Usar notificações do sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="662"/>
+        <source>Use Qt notifications</source>
+        <translation>Usar notificações do Qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="691"/>
+        <source>Test</source>
+        <translation>Testar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1501"/>
+        <source>minutes</source>
+        <translation>minutos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1533"/>
+        <source>Minutes between events purges</source>
+        <translation>Minutos entre expurgos de eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1559"/>
+        <source>days</source>
+        <translation>dias</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1572"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Máximo de dias de eventos para manter</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>rejeitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="563"/>
+        <source>System</source>
+        <translation>Sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="857"/>
+        <source>Command line</source>
+        <translation>Linha de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Theme</source>
+        <translation>Tema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="875"/>
+        <source>Rules</source>
+        <translation>Regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="883"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation>Quando esta opção é selecionada, as regras da duração selecionada não serão adicionadas à lista de regras temporárias na GUI.
+
+As regras temporárias ainda serão válidas e você poderá usá-las quando solicitado a permitir/negar uma nova conexão.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="888"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>Não salvar/excluir regras de duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="906"/>
+        <source>30s or less</source>
+        <translation>30s ou menos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="911"/>
+        <source>5m or less</source>
+        <translation>5m ou menos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="916"/>
+        <source>15m or less</source>
+        <translation>15m ou menos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>30m or less</source>
+        <translation>30m ou menos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="926"/>
+        <source>1h or less</source>
+        <translation>1h ou menos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="509"/>
+        <source>Language</source>
+        <translation>Idioma</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1071"/>
+        <source>General</source>
+        <translation>Geral</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="536"/>
+        <source>4MiB</source>
+        <translation>4MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="541"/>
+        <source>8MiB</source>
+        <translation>8MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="546"/>
+        <source>16MiB</source>
+        <translation>16MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="551"/>
+        <source>32MiB</source>
+        <translation>32MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="584"/>
+        <source>Maximum size for receiving messages from nodes. Default 4MB</source>
+        <translation>Tamanho máximo para receber mensagens de nós. Padrão 4 MB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="587"/>
+        <source>Max gRPC channel size</source>
+        <translation>Tamanho máximo do canal gRPC</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="594"/>
+        <source>By default the GUI is started when login</source>
+        <translation>Por padrão, a GUI é iniciada quando o login é feito</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="597"/>
+        <source>Autostart the GUI upon login</source>
+        <translation>Iniciar automaticamente a GUI após o login</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="628"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1221"/>
+        <source>Logging</source>
+        <translation>Registrando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1244"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will log timestamp microseconds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Se marcado, o OpenSnitch registrará microssegundos de registro de data e hora.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1247"/>
+        <source>Log timestamp microseconds</source>
+        <translation>Registrar microssegundos de carimbo de data/hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1291"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will use the UTC timezone for timestamps.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Se marcado, o OpenSnitch usará o fuso horário UTC para timestamps.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>Log UTC timestamps</source>
+        <translation>Registrar carimbos de data/hora UTC</translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Detalhes do processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>carregando...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: carregando...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>estatísticas mem: carregando...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Abrir arquivos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>Estatísticas de I/O</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Arquivos mapeados na memória</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Pilha</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Variáveis de ambiente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Aplicação pids</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Inicie ou pare de monitorar este processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Fechar</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Aplicar regra a todos os nodes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Para esta linha de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="466"/>
+        <source>From this executable</source>
+        <translation>Para este executável</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/caminho/para/o/executavel, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="604"/>
+        <source>To this IP / Network</source>
+        <translation>Para este IP / Rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>uma vez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="864"/>
+        <source>30s</source>
+        <translation type="obsolete">30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="869"/>
+        <source>5m</source>
+        <translation type="obsolete">5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="874"/>
+        <source>15m</source>
+        <translation type="obsolete">15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="879"/>
+        <source>30m</source>
+        <translation type="obsolete">30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="884"/>
+        <source>1h</source>
+        <translation type="obsolete">1h</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>sempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="896"/>
+        <source>To this port</source>
+        <translation>Para esta porta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Para este ID de usuário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="586"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Vírgulas ou espaços não podem especificar vários domínios.
+
+Em vez disso, use expressões regulares:
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+ou um único domínio:
+www.gnu.org - só vai filtrar www.gnu.org, não filtrará ftp.gnu.org, nem www2.gnu.org, ...
+gnu.org         - só vai filtrar gnu.org, não filtrará www.gnu.org, nem ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.dominio.org, .*\.dominio.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="520"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Apenas TCP, UDP ou UDPLITE são permitidos&lt;/p&gt;&lt;p&gt;Você pode usar expressão regulares, ou seja: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="255"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="260"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="265"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="270"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="275"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="754"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Você pode especificar um único IP:
+- 192.168.1.1
+
+ou uma expressão regular:
+- 192\.168\.1\.[0-9]+
+
+vários IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Você também pode especificar uma sub-rede:
+- 192.168.1.0/24
+
+Nota: Vírgulas ou espaços não são permitidos para separar IPs ou redes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="659"/>
+        <source>LAN</source>
+        <translation>LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="669"/>
+        <source>127.0.0.0/8</source>
+        <translation>127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="674"/>
+        <source>192.168.0.0/24</source>
+        <translation>192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="679"/>
+        <source>192.168.1.0/24</source>
+        <translation>192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="684"/>
+        <source>192.168.2.0/24</source>
+        <translation>192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="689"/>
+        <source>192.168.0.0/16</source>
+        <translation>192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="694"/>
+        <source>169.254.0.0/16</source>
+        <translation>169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="699"/>
+        <source>172.16.0.0/12</source>
+        <translation>172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="704"/>
+        <source>10.0.0.0/8</source>
+        <translation>10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="709"/>
+        <source>::1/128</source>
+        <translation>::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="714"/>
+        <source>fc00::/7</source>
+        <translation>fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="719"/>
+        <source>ff00::/8</source>
+        <translation>ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="724"/>
+        <source>fe80::/10</source>
+        <translation>fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="729"/>
+        <source>fd00::/8</source>
+        <translation>fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="627"/>
+        <source>Protocol</source>
+        <translation>Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="744"/>
+        <source>To this host</source>
+        <translation>Para este host</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Negar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation>Nome</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>As regras são verificadas em ordem alfabética, para que você possa nomeá-las de acordo para priorizá-las.
+
+000-permitir-localhost
+001-negar-transmissão
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="773"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">deixe em branco para criar automaticamente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Se marcada, esta regra terá precedência sobre o resto das regras. Nenhuma outra regra será verificada após esta.
+
+Você deve nomear a regra de forma que ela seja verificada primeiro, porque eles são verificados em ordem alfabética. Por exemplo:
+
+[x] Prioridade - 000-regra-prioritaria
+[  ] Prioridade - 001-regra-menos-prioritaria</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Regra de prioridade</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Por padrão, o campo das regras não diferencia maiúsculas de minúsculas, ou seja, se um processo tentar acessar gOOgle.CoM e você tiver uma regra para Negar. *Google.com, a conexão será bloqueada.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Se você marcar esta caixa, deverá especificar a string exata (domínio, executável, linha de comando) que deseja filtrar.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1089"/>
+        <source>Case-sensitive</source>
+        <translation>Sensível a maiúsculas e minúsculas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Você pode especificar várias portas usando expressões regulares:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 o 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 o 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="974"/>
+        <source>To this list of domains</source>
+        <translation>Para esta lista de domínios</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecione um diretório com listas de domínios para bloquear ou permitir.&lt;/p&gt;&lt;p&gt;Coloque dentro desse diretório arquivos com qualquer extensão que contenha listas de domínios.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;O formato de cada entrada de uma lista é o seguinte (formato de hosts):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.dominio.com&lt;/p&gt;&lt;p&gt;ou &lt;/p&gt;&lt;p&gt;0.0.0.0 www.dominio.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Aplicativos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="216"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will only match the executable path. It is not modifiable by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;You can use regular expressions to deny executions from /tmp for example:&lt;br/&gt;&lt;/p&gt;&lt;p&gt;^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este campo irá corresponder apenas ao caminho do executável. Não é modificável pelo usuário.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Você pode usar expressões regulares para negar execuções de /tmp, por exemplo:&lt;br/&gt;&lt;/p&gt;&lt;p&gt;^/tmp/.*$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Este campo irá conter e corresponder à linha de comando que foi executada pelo usuário.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Se o usuário digitou o comando, apenas o comando aparecerá:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Se o usuário digitou o caminho absoluto ou relativo para o comando, é isso que aparecerá:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>A partir deste PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="485"/>
+        <source>Network</source>
+        <translation>Rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="926"/>
+        <source>List of domains/IPs</source>
+        <translation>Lista de domínios/IPs</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>To this list of network ranges</source>
+        <translation>Para esta lista de intervalos de rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="939"/>
+        <source>To this list of IPs</source>
+        <translation>Para esta lista de IPs</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="965"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecione um diretório com arquivos contendo uma lista de IPs para bloquear ou permitir:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;Um IP por linha. Linhas vazias ou iniciadas com # são ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1000"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecione um diretório com arquivos contendo uma lista de intervalos de rede para bloquear ou permitir:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Um intervalo de rede por linha. Linhas vazias ou iniciadas com # são ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1028"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecione um diretório com listas de domínios para bloquear ou permitir.&lt;/p&gt;&lt;p&gt;Coloque dentro desse diretório arquivos com qualquer extensão contendo listas de domínios.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;O formato de cada entrada de uma lista é o seguinte (formato de hosts):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Linhas vazias ou iniciadas com # são ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1043"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>Para esta lista de domínios 
+(expressões regulares)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1070"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Selecione um diretório com arquivos contendo expressões regulares de domínios para bloquear ou permitir:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;Você também pode usar um domínio como: &amp;quot;example.com&amp;quot; , e vai combinar whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;Um domínio por linha. Linhas vazias ou iniciadas com # são ignoradas.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Rejeitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1145"/>
+        <source>Description...</source>
+        <translation>Descrição...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;O valor deste campo é sempre o caminho absoluto para o executável: /caminho/do/binário&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Exemplos:&lt;/p&gt;&lt;p&gt;- Simples: /caminho/do/binário&lt;/p&gt;&lt;p&gt;- Vários caminhos: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Vários binários: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Negar/Permitir execuções a partir de /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Para mais exemplos, visite a &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;página wiki&lt;/a&gt; ou pergunte nos &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;fóruns de discussão&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>É expressão regular</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="473"/>
+        <source>is regular expression</source>
+        <translation>é expressão regular</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/>
+        <source>Network interface</source>
+        <translation>Interface de rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1080"/>
+        <source>More</source>
+        <translation>Mais</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1096"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>Não registre conexões que correspondam a esta regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1099"/>
+        <source>Don&apos;t log connections</source>
+        <translation>Não registre conexões</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Negar apenas descartará a conexão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Rejeitar derrubará a conexão e matará o soquete que a iniciou</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>Permitir permitirá a conexão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="560"/>
+        <source>ICMP</source>
+        <translation>ICMP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="565"/>
+        <source>ICMP6</source>
+        <translation>ICMP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="570"/>
+        <source>SCTP</source>
+        <translation>SCTP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="575"/>
+        <source>SCTP6</source>
+        <translation>SCTP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="737"/>
+        <source>From this IP / Network</source>
+        <translation>A partir deste IP / Rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="866"/>
+        <source>From this port</source>
+        <translation>A partir desta porta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="912"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Você pode especificar várias portas usando expressões regulares:&lt;/p&gt;&lt;p&gt;- 53, 80 ou 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="664"/>
+        <source>MULTICAST</source>
+        <translation>MULTICAST</translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>Estatísticas da rede OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="287"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Salvar em CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="297"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Crie uma nova regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1814"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Iniciar ou parar a interceptação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filtro</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Negar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Ex.: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="829"/>
+        <source>Nodes</source>
+        <translation>Nodes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(clique duas vezes na coluna endereço para ver os detalhes de um node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1721"/>
+        <source>Rules</source>
+        <translation>Regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="998"/>
+        <source>enable</source>
+        <translation>habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="674"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(clique duas vezes na coluna Nome para ver os detalhes de uma regra)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">nome da regra de pesquisa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="785"/>
+        <source>Application rules</source>
+        <translation>Regras de aplicação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="939"/>
+        <source>Permanent</source>
+        <translation>Permanente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="948"/>
+        <source>Temporary</source>
+        <translation>Temporário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1069"/>
+        <source>Hosts</source>
+        <translation>Hosts</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(clique duas vezes para ver os detalhes de um item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1162"/>
+        <source>Applications</source>
+        <translation>Aplicativos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1278"/>
+        <source>Addresses</source>
+        <translation>Endereços</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Ports</source>
+        <translation>Portas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1458"/>
+        <source>Users</source>
+        <translation>Usuários</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1565"/>
+        <source>Connections</source>
+        <translation>Conexões</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1617"/>
+        <source>Dropped</source>
+        <translation>Dropado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1669"/>
+        <source>Uptime</source>
+        <translation>Tempo de atividade</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1788"/>
+        <source>Version</source>
+        <translation>Versão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Excluir todos os eventos interceptados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1028"/>
+        <source>Edit rule</source>
+        <translation>Editar regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1045"/>
+        <source>Delete rule</source>
+        <translation>Excluir regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Excluir todos os hosts interceptadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Excluir todos os aplicativos interceptadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Excluir todos os endereços interceptados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Excluir todas as portas interceptadas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Excluir todos os usuários interceptados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(clique duas vezes em uma linha para ver os detalhes de uma regra)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="915"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Exclua conexões que correspondam a esta regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="930"/>
+        <source>All applications</source>
+        <translation>Todos os aplicativos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Rejeitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="780"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="957"/>
+        <source>System rules</source>
+        <translation>Regras do sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="640"/>
+        <source>Delete this node</source>
+        <translation>Excluir este node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="656"/>
+        <source>Show the preferences of this node</source>
+        <translation>Mostrar as preferências deste node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="672"/>
+        <source>Start or stop interception of this node</source>
+        <translation>Iniciar ou parar a interceptação deste node</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Statistics</source>
+        <translation>Estatísticas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Help</source>
+        <translation>Ajuda</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="52"/>
+        <source>Close</source>
+        <translation>Fechar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Disable</source>
+        <translation>Desabilitar</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="96"/>
+        <source>Configuration applied.</source>
+        <translation>Configuração aplicada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="405"/>
+        <source>Error: {0}</source>
+        <translation>Error: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="198"/>
+        <source>Applying changes...</source>
+        <translation>Aplicando alterações...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="235"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>Erro ao obter a política da cadeia de ENTRADA</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="242"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>Erro ao obter a política de cadeia de SAÍDA</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="295"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>Para configurar regras de firewall a partir da GUI, precisamos usar &apos;nftables&apos; em vez de &apos;iptables&apos;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="309"/>
+        <source>Enabling firewall...</source>
+        <translation>Habilitando firewall...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="311"/>
+        <source>Disabling firewall...</source>
+        <translation>Desabilitando firewall...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Porta de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Porta de origem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>IP de destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>IP de origem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>Interface de entrada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>Interface de saída</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>Definir marca de controle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>Correspondência de marca de controle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>Corresponde ao(s) estado(s) de controle de correspondência</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>Definir marca no pacote</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>Correspondência de informações do pacote</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>Cotas de largura de banda</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>Limite de taxa de conexões</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="374"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>Sua versão do protobuf é incompatível, você precisa instalar o protobuf 3.8.0 ou superior
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="398"/>
+        <source>Rule deleted</source>
+        <translation>Regra deletada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="402"/>
+        <source>Rule added</source>
+        <translation>Regra adicionada</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="424"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation>Você pode usar &apos;,&apos; ou &apos;-&apos; para especificar várias portas/IPs ou intervalos/valores:&lt;br&gt;&lt;br&gt;ports: 22 ou 22,443 ou 50000-60000&lt;br&gt;IPs: 192.168.1.1 ou 192.168.1.30-192.168.1.130&lt;br&gt;Valores: eco-resposta,eco-pedido&lt;br&gt;Valores: novo,estabelecido,relacionado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="444"/>
+        <source>Deleting rule, wait</source>
+        <translation>Excluindo regra, aguarde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="447"/>
+        <source>Error updating rule</source>
+        <translation>Erro ao atualizar regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="493"/>
+        <source>Adding rule, wait</source>
+        <translation>Adicionando regra, aguarde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="502"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation>&lt;selecione uma declaração&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="803"/>
+        <source>Equal</source>
+        <translation>Igual</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="804"/>
+        <source>Not equal</source>
+        <translation>Não igual</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="805"/>
+        <source>Greater or equal than</source>
+        <translation>Maior ou igual a</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="806"/>
+        <source>Greater than</source>
+        <translation>Maior que</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="807"/>
+        <source>Less or equal than</source>
+        <translation>Menor ou igual a</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="808"/>
+        <source>Less than</source>
+        <translation>Menor que</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1491"/>
+        <source>Firewall rule</source>
+        <translation>Regra do firewall</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1028"/>
+        <source>Simple</source>
+        <translation>Simple</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1033"/>
+        <source>Advanced</source>
+        <translation>Avançado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1154"/>
+        <source>This rule is not supported yet.</source>
+        <translation>Esta regra ainda não é suportada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1219"/>
+        <source>Exclude service</source>
+        <translation>Excluir serviço</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1231"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>Permitir conexões de entrada para a porta selecionada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1233"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>Permitir conexões de saída para a porta selecionada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1309"/>
+        <source>select a statement.</source>
+        <translation>selecione uma declaração.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1325"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>o valor não pode ser 0 ou vazio.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1337"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>o formato do valor é 1024/kbytes (ou bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1351"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>o formato do valor é 1024/kbytes/segundo (ou bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1354"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>limite de taxa inválido, use: bytes, kbytes, mbytes ou gbytes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1356"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>limite de tempo inválido, use: segundo, minuto, hora ou dia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1423"/>
+        <source>port not valid.</source>
+        <translation>porta inválida.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation>
+Formatos suportados:
+
+ - Simple: 23
+ - Gamas: 80-1024
+ - Múltiplas portas: 80,443,8080
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation>
+Formatos suportados:
+
+ - Simple: 1.2.3.4
+ - Intervalos de IP: 1.2.3.100-1.2.3.200
+ - Intervalos de rede: 1.2.3.4/24
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation>Interface de entrada de correspondência. Expressões regulares não permitidas.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation>Interface de saída de correspondência. Expressões regulares não permitidas.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation>Defina uma marca de controle na conexão, no formato decimal.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation>Corresponde a uma marca de controle da conexão, no formato decimal.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation>Corresponde aos estados de controle.
+
+Formatos suportados:
+ - Simple: novo
+ - Vários estados separados por vírgulas: relacionado,novo
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation>
+Corresponde às metainformações do pacote.
+
+O valor deve estar no formato decimal, exceto na opção &quot;l4proto&quot;.
+Para l4proto pode ser uma string em letras minúsculas, por exemplo:
+ tcp
+ udp
+ icmp,
+ etc
+
+Se o valor for decimal para protocolo ou lproto, ele o usará como código
+ desse protocolo.
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation>Defina uma marca no pacote que corresponda às condições especificadas. O valor está no formato decimal.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation>
+Corresponde aos códigos ICMP.
+
+Formatos suportados:
+ - Simple: eco-pedido
+ - Múltiplos separados por vírgulas: eco-pedido, eco-resposta
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation>
+Corresponde aos códigos ICMPv6.
+
+Formatos suportados:
+ - Simple: Formatos suportados
+ - Múltiplos separados por vírgulas: eco-pedido, eco-resposta
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation>Imprima uma mensagem quando esta regra corresponder a um pacote.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation>
+Aplicar cotas em conexões.
+
+Por exemplo quando:
+ - &quot;cota acima de 10/mbytes&quot; -&gt; aplicar a Ação definida (DERRUBAR)
+ - &quot;cota até 10/mbytes&quot; -&gt; aplicar a Ação definida (ACEITAR)
+
+O valor deve estar no formato: VALOR/UNIDADES, por exemplo:
+ - 10mbytes, 1/gbytes, etc
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation>
+Aplicar limites nas conexões.
+
+Por exemplo quando:
+ - &quot;limite acima de 10/mbytes/minuto&quot; -&gt; aplique a Ação definida (DERRUBAR, ACEITAR, etc)
+    (Quando houver mais de 10MB por minuto, aplique uma Ação)
+
+ - &quot;limite até 10/mbytes/hora&quot; -&gt; aplique a Ação definida (ACEITAR)
+
+O valor deve estar no formato: VALOR/UNIDADES/TEMPO, por exemplo:
+ - 10/mbytes/minuto, 1/gbytes/hora, etc
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="623"/>
+        <source>num</source>
+        <translation>num</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="637"/>
+        <source>to</source>
+        <translation>para</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="482"/>
+        <source>Add at least one statement.</source>
+        <translation>Adicione pelo menos uma instrução.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="976"/>
+        <source>Warning: ct set mark value is empty, malformed rule?</source>
+        <translation>Aviso: o valor da marca do conjunto ct está vazio, regra malformada?</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="299"/>
+        <source>Info</source>
+        <translation>Informações</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="303"/>
+        <source>Error</source>
+        <translation>Erro</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="307"/>
+        <source>Warning</source>
+        <translation>Aviso</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="684"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>As notificações do sistema não estão disponíveis, você precisa instalar o python3-notify2.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>para sempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Negar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Processo desconhecido</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Conexão de saída</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Processo lançado de:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>a partir deste executável</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>a partir desta linha de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>para a porta {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>para {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>do usuário {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>para {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>para *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">para *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Processo remoto&lt;/b&gt; %s rodando em &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>está conectando a &lt;b&gt;%s&lt;/b&gt; em %s na porta %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>está tentando resolver &lt;b&gt;%s&lt;/b&gt; via %s, %s porta %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>a partir desse PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="114"/>
+        <source>New outgoing connection</source>
+        <translation>Nova conexão de saída</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Rejeitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>está se conectando a &lt;b&gt;%s&lt;/b&gt;, %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation>Abrir</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="311"/>
+        <source>Server address can not be empty</source>
+        <translation>O endereço do servidor não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="598"/>
+        <source>Configuration applied.</source>
+        <translation>Configuração aplicada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="409"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Configuração de salvamento de exceção: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="529"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Aplicando configuração em {0} ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="342"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Erro ao carregar configuração de {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="600"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Erro ao aplicar configuração: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Warning</source>
+        <translation>Aviso</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Você deve selecionar um arquivo para o banco de dados&amp;lt;br&amp;gt;ou escolher o tipo &amp;quot;Na memória&amp;quot;.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="424"/>
+        <source>DB type changed</source>
+        <translation>Tipo de banco de dados alterado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation type="obsolete">Reinicie a GUI para que os efeitos tenham efeito</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="637"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Passe o mouse sobre os textos para exibir a ajuda&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;Não se esqueça de visitar a wiki: &amp;lt;a href=&amp;quot;{0}&amp;quot;&amp;gt;{0}&amp;lt;/a&amp;gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="495"/>
+        <source>System</source>
+        <translation>Sistema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="191"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>Temas não disponíveis. Instale qt-material: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="496"/>
+        <source>UI theme changed</source>
+        <translation>Tema da IU alterado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="496"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Reinicie a GUI para aplicar o novo tema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="537"/>
+        <source>Ok</source>
+        <translation>Ok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="39"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation>Reinicie a GUI para que as mudanças tenham efeito</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="411"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>Não há nodes conectados</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="550"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>Exceção ao salvar a configuração do node {0}: {1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="168"/>
+        <source>System default</source>
+        <translation>Sistema padrão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="462"/>
+        <source>Language changed</source>
+        <translation>Idioma alterado</translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Erro ao carregar as informações do processo:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Erro ao parar o processo de monitoramento:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>carregando...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="235"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Não há nodes conectados.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="278"/>
+        <source>Rule applied.</source>
+        <translation>Regra aplicada.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="648"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>protocolo não pode estar vazio ou desmarque-o</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="662"/>
+        <source>Protocol regexp error</source>
+        <translation>Erro de expressão de protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="666"/>
+        <source>process path can not be empty</source>
+        <translation>o caminho do processo não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="680"/>
+        <source>Process path regexp error</source>
+        <translation>Erro de expressão regular do caminho do processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="684"/>
+        <source>command line can not be empty</source>
+        <translation>a linha de comando não pode estar vazia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="698"/>
+        <source>Command line regexp error</source>
+        <translation>Erro de expressão regular da linha de comando</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="738"/>
+        <source>Dest port can not be empty</source>
+        <translation>A porta de destino não pode estar vazia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="752"/>
+        <source>Dst port regexp error</source>
+        <translation>Erro de expressão regular da porta Dst</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="756"/>
+        <source>Dest host can not be empty</source>
+        <translation>Dest host não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="770"/>
+        <source>Dst host regexp error</source>
+        <translation>Erro de expressão regular do host Dst</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="812"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>O IP/rede de destino não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="838"/>
+        <source>Dst IP regexp error</source>
+        <translation>Erro de expressão regular de IP Dst</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="850"/>
+        <source>User ID can not be empty</source>
+        <translation>O ID do usuário não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="864"/>
+        <source>User ID regexp error</source>
+        <translation>Erro de expressão regular do ID do usuário</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="280"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Erro ao aplicar regra: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="983"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Regra não suportada&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="938"/>
+        <source>Lists field cannot be empty</source>
+        <translation>O campo de listas não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="940"/>
+        <source>Lists field must be a directory</source>
+        <translation>O campo de listas deve ser um diretório</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="546"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Erro ao carregar regra&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="252"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Já existe uma regra com este nome.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="868"/>
+        <source>PID field can not be empty</source>
+        <translation>O campo PID não pode ficar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="882"/>
+        <source>PID field regexp error</source>
+        <translation>Erro de expressão regular do campo PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="970"/>
+        <source>Select at least one field.</source>
+        <translation>Selecione pelo menos um campo.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="702"/>
+        <source>Network interface can not be empty</source>
+        <translation>A interface de rede não pode estar vazia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="716"/>
+        <source>Network interface regexp error</source>
+        <translation>Erro de expressão regular da interface de rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="720"/>
+        <source>Source port can not be empty</source>
+        <translation>A porta de origem não pode estar vazia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="734"/>
+        <source>Source port regexp error</source>
+        <translation>Erro de regexp da porta de origem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="774"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation>O IP/rede de origem não pode estar vazio</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="800"/>
+        <source>Source IP regexp error</source>
+        <translation>Erro de regexp do IP de origem</translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="312"/>
+        <source>Not running</source>
+        <translation>Não está em execução</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Disabled</source>
+        <translation>Desabilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Running</source>
+        <translation>Em execução</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1188"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Você está prestes a excluir esta regra.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1710"/>
+        <source>    Are you sure?</source>
+        <translation>    Você tem certeza?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="635"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>Estatísticas da rede OpenSnitch {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="637"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>Estatísticas da rede OpenSnitch para {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nome</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Endereço</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="176"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="183"/>
+        <source>Version</source>
+        <translation type="obsolete">Versão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Rules</source>
+        <translation>Regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Tempo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="874"/>
+        <source>Action</source>
+        <translation>Ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Acertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2597"/>
+        <source>Save as CSV</source>
+        <translation>Salvar como CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Ativado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Delete</source>
+        <translation>Deletar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&amp;lt;b&amp;gt;Erro:&amp;lt;/b&amp;gt;&amp;lt;br&amp;gt;&amp;lt;br&amp;gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="953"/>
+        <source>Disable</source>
+        <translation>Desabilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="955"/>
+        <source>Enable</source>
+        <translation>Habilitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="958"/>
+        <source>Duplicate</source>
+        <translation>Duplicado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Edit</source>
+        <translation>Editar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1247"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Regra não encontrada por esse nome e node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1300"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Erro:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1307"/>
+        <source>Warning:</source>
+        <translation>Atenção:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="939"/>
+        <source>Allow</source>
+        <translation>Permitir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Deny</source>
+        <translation>Negar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="944"/>
+        <source>Always</source>
+        <translation>Sempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Until reboot</source>
+        <translation>Até reiniciar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1710"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Você está prestes a excluir esta regra.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nome</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nome</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Endereço</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Tempo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Ativado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acertos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="286"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nome</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Endereço</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versão</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="419"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tempo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ação</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Duração</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Node</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Acessos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Processo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regra</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>UltimaConexao</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Args</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstHost</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>TempoAtividade</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="179"/>
+        <source>Uptime</source>
+        <translation type="obsolete">Tempo de atividade</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="181"/>
+        <source>Connections</source>
+        <translation type="obsolete">Conexões</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="182"/>
+        <source>Dropped</source>
+        <translation type="obsolete">Dropado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Qual</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Reject</source>
+        <translation>Rejeitar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="930"/>
+        <source>Apply to</source>
+        <translation>Aplicar para</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Nome da rede</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Tempo de atividade</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Conexoes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Dropado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="436"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Qual</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Precedencia</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="794"/>
+        <source>New node connected</source>
+        <translation>Novo node conectado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Descrição</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Terminal</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="405"/>
+        <source>Export rules</source>
+        <translation>Exportar regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Import rules</source>
+        <translation>Importar regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Export events to CSV</source>
+        <translation>Exportar eventos para CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Quit</source>
+        <translation>Sair</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Export</source>
+        <translation>Exportar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="963"/>
+        <source>To clipboard</source>
+        <translation>Para a área de transferência</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To disk</source>
+        <translation>Para o disco</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2539"/>
+        <source>Select a directory to export rules</source>
+        <translation>Selecione um diretório para exportar regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1190"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    Você está prestes a excluir esta entrada.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1677"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    Você está prestes a excluir este node.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1686"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Erro ao excluir node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2494"/>
+        <source>Error exporting rules</source>
+        <translation>Erro ao exportar regras</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2568"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>Selecione um diretório com regras para importar (arquivos JSON)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2582"/>
+        <source>Rules imported fine</source>
+        <translation>Regras importadas corretamente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="229"/>
+        <source>WARNING</source>
+        <translation>AVISO</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="832"/>
+        <source>Details</source>
+        <translation>Detalhes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>New</source>
+        <translation>Novo</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts b/ui/i18n/locales/ro_RO/opensnitch-ro_RO.ts
new file mode 100644 (file)
index 0000000..436f5e0
--- /dev/null
@@ -0,0 +1,2957 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ro_RO">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>din acest executabil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>din această linie de comandă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>acest port de destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>acest utilizator</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>această adresă IP de destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>o dată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1o</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>până la repornire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>mereu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Refuză</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Permite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>ID utilizator</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executat din&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>EtichetăText</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Adresă IP sursă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>ID proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Adresă IP destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Port destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation type="unfinished">Închide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation type="unfinished">Nod</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation type="unfinished">Activează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation type="unfinished">Acțiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation type="unfinished">Șterge</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation type="unfinished">Salvează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Preferințe</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Interfață utilizator</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Arată implicit vizualizarea avansată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>o dată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1o</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>până la repornire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>mereu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Acțiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Țintă implicită</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Dacă este bifată, ferestrele de notificare care apar vor fi afișate cu vizualizarea avansată activă.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>refuză</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>permite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>după executabil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>după linia de comandă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>după portul de destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>după adresa IP de destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>după identificatorul utilizatorului</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>centru</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>sus la dreapta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>jos la dreapta</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>sus la stânga</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>jos la stânga</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Durată implicită fereastră de notificare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Durată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Filtrează conexiunile și după:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>ID utilizator</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Port destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Adresă IP destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Dezactivează ferestrele de notificare, arată doar o alertă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Durată implicită pentru alegere</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Noduri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Metodă monitorizare procese</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Fișierul jurnal unde să se scrie jurnalizări.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout va tipări jurnalizările pe ieșirea standard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Fișier de jurnalizare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Durata implicită va fi folosită când nu este conectată nicio interfață grafică.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Durată implicită</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Aplică configurația la toate nodurile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Acțiunea implicită va fi folosită când nu este conectată nicio interfață grafică.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>NumeGazdă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>până la repornire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>întotdeauna</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adresă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Versiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Nivel implicit de jurnalizare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Bază de date</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Tip bază de date</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Selectare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>În memorie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Fișier</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Închide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Aplică</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Salvează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>Vizualizarea avansată vă permite să selectați cu ușurință câmpuri multiple pentru a filtra conexiunile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Dacă este bifată, acest câmp va fi selectat când o fereastră de notificare este afișată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Acțiune implicită când interfața grafică cu utilizatorul este deconectată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Depanează conexiunile nevalide</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Ferestre de notificare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Opțiuni implicite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Compozziția implicită pe ecran</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>oricare regulă temporară</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="450"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="453"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Nu salva regulile de durată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Timp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Regulă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Nod</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don't have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There're some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Coloane etichete evenimente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reguli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Detalii proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>Se încarcă...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: loading...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>Statistici memorie: se încarcă...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Stare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Deschidere fișiere</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>Statistici intrare/ieșire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Fișiere cartografiate în memorie</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Stivă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Variabile de mediu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Identificatori procese aplicație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Pornește sau oprește monitorizarea acestui proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Închide</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Regulă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Nod</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Aplică regula la toate nodurile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>Pentru această adresă IP / rețea</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/cale/către/executabil, .*/bin/executabil[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Acțiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>Pentru acest port</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Pentru această listă de domenii</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>LAN</source>
+        <translation type="obsolete">Rețea locală (LAN)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="219"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="224"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="229"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="234"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="239"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="244"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="249"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="254"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="259"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="264"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="269"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="274"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="279"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>o dată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>30s</source>
+        <translation type="obsolete">30s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="328"/>
+        <source>5m</source>
+        <translation type="obsolete">5m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="333"/>
+        <source>15m</source>
+        <translation type="obsolete">15m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="338"/>
+        <source>30m</source>
+        <translation type="obsolete">30m</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="343"/>
+        <source>1h</source>
+        <translation type="obsolete">1o</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>până la repornire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>întotdeauna</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domeniu.org, .*\.domeniu.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>Pentru această gazdă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Durată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="436"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="441"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>De la acest executabil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Refuză</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Permite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>De la această linie de comandă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>De la acest ID utilizator</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Nume</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Activează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Regulile sunt verificate în ordine alfabetică, așa că puteți să le numiți ca atare pentru a le prioritiza.
+
+000-permite-localhost
+001-respinge-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="611"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">lăsați gol pentru creare automată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Regulă prioritate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Sensibil la majuscule</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation type="unfinished">Aplicații</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>Statistici de rețea OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="105"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Salvează într-un fișier CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="115"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Creare regulă nouă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Stare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Porniți sau opriți interceptarea</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Evenimente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filtru</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Permite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Refuză</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>De exemplu: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Șterge toate evenimentele de interceptare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Noduri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Reguli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>Activează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Editare regulă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Șterge regula</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(faceți clic dublu pe un rând pentru a vizualiza detaliile regulii)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">Căutare nume regulă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Reguli aplicație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation type="unfinished">Permanent</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Temporar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Gazde</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(faceți clic dublu pentru a vizualiza detaliile unui element)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Aplicații</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Șterge toate aplicațiile interceptate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adrese</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Porturi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Utilizatori</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Conexiuni</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Aruncate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Durată activitate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Versiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="665"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Șterge toate conexiunile care se potrivesc cu această regulă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Toate aplicațiile</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Șterge toate gazdele interceptate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Șterge toate adresele interceptate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Șterge toate porturile interceptate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Șterge toți utilizatorii interceptați</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Statistici</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Activează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Dezactivează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Ajutor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Închide</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation type="unfinished">Configurația a fost aplicată.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation type="unfinished">Adresă IP sursă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation type="unfinished">Avertisment</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>până la repornire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>mereu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Permite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Refuză</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Conexiune de ieșire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Procesul a fost lansat din:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>de la acest executabil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>de la această linie de comandă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>către portul {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>către {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>de la utilizatorul {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>către {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>către *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">către *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>Procesul %s&lt;b&gt;telecomandat&lt;/b&gt; rulează pe &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>se conectează la &lt;b&gt;%s&lt;/b&gt; pe %s portul %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>încearcă să rezolve &lt;b&gt;%s&lt;/b&gt; prin %s, %s portul %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Exception saving config: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Avertisment</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>Tipul bazei de date a fost schimbat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Reporniți interfața grafică cu utilizatorul pentru ca modificările să aibă efect</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Se aplică configurația pe {0} ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Adresa servitorului nu poate fi goală</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Error loading {0} configuration</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Configurația a fost aplicată.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Eroare la aplicarea configurației: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don't forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Eroare la încărcarea informațiilor procesului:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Eroare la oprirea monitorizării procesului:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>Se încarcă...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Nu există niciun nod conectat.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Regulă aplicată.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Eroare la aplicarea regulii: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Eroare la încărcarea regulii&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>Protocolul nu poate fi gol, sau debifați-l</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Protocol regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>Calea procesului nu poate fi goală</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Process path regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>Linia de comandă nu poate fi goală</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Command line regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Dest port can not be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Dst port regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Dest host can not be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Dst host regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Dest IP/Network can not be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Dst IP regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>User ID can not be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>User ID regexp error</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Lists field cannot be empty</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Lists field must be a directory</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Regula nu este sprijinită&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Nu rulează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Dezactivată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Rulează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch Network Statistics {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch Network Statistics for {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Eroare:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Avertisment:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Permite</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Refuză</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Întotdeauna</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Până la repornire</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Dezactivează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Activează</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Duplică</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Editare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Șterge</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Sunteți pe cale să ștergeți aceasă regulă.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Sigur doriți acest lucru?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Regula nu a putut fi găsită după acel nume și nod</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Sunteți pe cale să ștergeți această regulă.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Save as CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nume</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adresă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Stare</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nume gazdă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Versiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Reguli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Timp</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Acțiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Durată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Nod</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Activată</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Atingeri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Protocol</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Proces</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Destinație</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Regulă</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>IdentificatorUtilizator</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>UltimaConexiune</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="275"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Argumente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstHost</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>DstPort</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation type="unfinished">Atingeri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Durată activitate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Conexiuni</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished">Aruncate</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reguli</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acțiune</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts b/ui/i18n/locales/ru_RU/opensnitch-ru_RU.ts
new file mode 100644 (file)
index 0000000..d082939
--- /dev/null
@@ -0,0 +1,3401 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="ru_RU">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>ID пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Выполнено из&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>Заметка</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Исходный IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>ID процесса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>IP назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Порт назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>из этого исполняемого файла</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>из этой командной строки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>этот порт назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>этот пользователь</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>этот ip-адрес назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>один раз</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30 секунд</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1 час</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>постоянно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Запретить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>Разрешить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>до перезагрузки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>из этого PIDа</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>действие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Фаервол</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Фаервол&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>Входящие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>Исходящие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Профиль</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>Разрешить входящие соединения на порт</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>Разрешить сервис (ВХОДЯЩИЕ)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>Исключение исходящих подключений к порту от перехвата</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation>Разрешить сервис (ИСХОДЯЩИЕ)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation>Новое правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="453"/>
+        <source>xxx</source>
+        <translation type="obsolete">ххх</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation>Закрыть</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Правило фаервола</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Узел</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="34"/>
+        <source>All</source>
+        <translation type="obsolete">Все</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>Включить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>Описание</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>Просто</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>Добавить новое условие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>Удалить выбранное условие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation>Направление</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation>ВХОДЯЩИЕ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation>ИСХОДЯЩИЕ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation>Действие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation>ПРИНИМАТЬ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation>ОТБРАСЫВАТЬ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation>ОТКЛОНЯТЬ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation>ВОЗВРАЩАТЬ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation>Очистить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation>Удалить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation>Сохранить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation>Добавить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation>ПЕРЕНАПРАВЛЕНИЕ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Настройки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Пользовательский интерфейс</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Тайм-аут по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Продолжительность всплывающего окна по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Продолжительность по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Цель по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>в центре</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>в правом верхнем углу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>в нижнем правом углу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>в левом верхнем углу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>в нижнем левом углу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>исполняемым файлом</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>по командной строке</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>по порту назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>по IP-адресу назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>по ID пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>один раз</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30 секунд</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1 час</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>постоянно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>запрещено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>разрешено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Отключить всплывающие окна, отображать только предупреждение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Узлы</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>Метод мониторинга процесса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Продолжительность по умолчанию будет иметь место, когда пользовательский интерфейс не подключен.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Адрес узла.&lt;/p&gt;&lt;p&gt;По умолчанию: unix:///tmp/osui.sock (unix:// является обязательным, если это Unix сокет) &lt;/p&gt;&lt;p&gt;Это также может быть IP-адрес с портом: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Адрес</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Уровень логирования по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Версия</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Действие по умолчанию выполняется при отсутствии подключенного пользовательского интерфейса.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Лог файл для логирования.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout выводит логи на стандартный вывод.&lt;/p&gt;&lt;/body&gt; &lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Лог файл</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Имя хоста</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>до перезапуска</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>всегда</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Применить конфигурацию ко всем узлам</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>База данных</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>В памяти</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Файл</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Закрыть</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Применить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Сохранить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>до перезагрузки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Тип базы данных</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Выбрать</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Показывать расширенный вид по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Действие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Если этот флажок установлен, всплывающие окна будут отображаться с активным расширенным видом.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Длительность</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;По умолчанию, когда появляется новое всплывающее окно, в его простейшей форме вы сможете фильтровать соединения или приложения по одному свойству соединения (исполняемый файл, порт, IP-адрес и т. д.).&lt;/p&gt;&lt;p&gt;С помощью этих вариантов, вы можете выбрать несколько полей для фильтрации подключений.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Также фильтровать соединения по:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>ID пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Порт назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>IP назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Этот тайм-аут представляет собой обратный отсчет, который вы видите, когда отображается всплывающее диалоговое окно.&lt;/p&gt;&lt;p&gt;Если всплывающее окно не отвечает, будут применены параметры по умолчанию.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>Расширенный вид позволяет легко выбирать несколько полей для фильтрации подключений</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>Если флажок установлен, это поле будет выбрано при отображении всплывающего окна</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Действие всплывающего окна по умолчанию.&lt;/p&gt;&lt;p&gt;Когда будет установлено новое исходящее соединение, это действие будет выбрано по умолчанию, поэтому, если тайм-аут срабатывает , будет применен этот параметр.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Когда всплывающее окно просит пользователя разрешить или запретить соединение:&lt;/p&gt;&lt;p &gt;1. новые исходящие соединения запрещены.&lt;/p&gt;&lt;p&gt;2. известные соединения разрешаются или запрещаются на основе правил, определенных пользователем.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>Обычное действие, когда интерфейс отключен</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Отладка недействительных соединений</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Всплывающие окна</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Параметры по умолчанию</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Положение по умолчанию на экране</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>любые временные правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="478"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Если выбран этот параметр, правила выбранной продолжительности не будут добавляться в список временных правил в графическом интерфейсе.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Временные правила останутся в силе, и вы сможете использовать их, когда будет предложено разрешить/запретить новое подключение.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="481"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Не сохранять правила длительности</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Время</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Назначение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>Протокол</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>Процесс</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Узел</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Eсли этот флажок установлен, opensnitch предложит вам разрешить или запретить соединения, не имеющие связанного PID, по нескольким причинам, в основном из-за плохого состояния соединений.&lt;/p&gt; &lt;p&gt;Всплывающее диалоговое окно будет содержать только информацию о сетевом подключении.&lt;/p&gt;&lt;p&gt;Хотя в некоторых сценариях это действительные подключения, например, при установке VPN с помощью wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Столбцы вкладки &quot;События&quot;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>по PIDу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Уведомления на рабочем столе</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Использовать системные уведомления</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Исользовать уведомления Qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation>Тест</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation>Система</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Тема</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation>Минут между очисткой событий</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>дни</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Максимальное количество дней для сохранения событий</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>отклонять</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Запретить всплывающие окна, показывать только уведомления</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation>Командная строка</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation>Правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>Не сохранять/Удалить правила по продолжительности</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation>Не более 30 сек</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation>Не более 5 мин</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation>Не более 15 мин</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation>Не более 30 мин</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation>Не более 1 часа</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation>Язык</translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>Детали процесса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>загрузка...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: загрузка...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>статистика памяти: загружается...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Состояние</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Открыть файлы</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O Статистика</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Файлы с отображением памяти</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Стек</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Переменные среды</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>PIDы приложений</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Начать или остановить мониторинг этого процесса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Закрыть</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Узел</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Применить правило ко всем узлам</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Из этой командной строки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Из этого исполняемого файла</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Действие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/путь/к/исполняемому/файлу, .*/bin/executable[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>К этому IP / Сети</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>один раз</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="102"/>
+        <source>30s</source>
+        <translation type="obsolete">30 секунд</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="107"/>
+        <source>5m</source>
+        <translation type="obsolete">5 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="112"/>
+        <source>15m</source>
+        <translation type="obsolete">15 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="117"/>
+        <source>30m</source>
+        <translation type="obsolete">30 минут</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="122"/>
+        <source>1h</source>
+        <translation type="obsolete">1 час</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>всегда</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>К этому порту</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>От этого ID пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Запятые или пробелы не могут указывать несколько доменов.
+
+Вместо этого используйте регулярные выражения:
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+или один домен:
+www.gnu.org - это будет соответствовать только www.gnu.org, но не ftp.gnu.org, и не www2.gnu.org,...
+gnu.org - это будет соответствовать только gnu.org, но не www.gnu.org, и не ftp.gnu.org, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domain.org, .*\.domain.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Только TCP, UDP или UDPLITE разрешены&lt;/p&gt;&lt;p&gt;Вы можете использовать regexp: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="411"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Вы можете указать один IP:
+- 192.168.1.1
+
+или регулярное выражение:
+- 192\.168\.1\.[0-9]+
+
+несколько IP-адресов:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Вы также можете указать подсеть:
+- 192.168.1.0/24
+
+Примечание. Запятые или пробелы не могут использоваться для разделения IP-адресов или сетей.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>LAN</source>
+        <translation type="obsolete">LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="537"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="547"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="552"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="562"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="567"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="572"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="577"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="582"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="587"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Длительность</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>Протокол</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>К этому хосту</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Запретить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>Разрешить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Имя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Включить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Правила проверяются в алфавитном порядке, поэтому вы можете назвать их соответствующим образом, чтобы расставить приоритеты.
+
+000-allow-localhost
+001-deny-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="935"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">оставьте пустым для автосоздания</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>Если этот флажок установлен, это правило будет иметь приоритет над остальными правилами. Никакие другие правила не будут проверяться после этого.
+
+Вы должны назвать правило таким образом, чтобы оно проверялось первым, потому что они проверяются в алфавитном порядке. Например:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Правило приоритета</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;По умолчанию поле правил не чувствительно к регистру, т. е. если процесс пытается получить доступ к gOOgle.CoM, а у вас есть правило Запретить .*google.com, соединение будет заблокировано.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Если вы установите этот флажок, вы должны указать точную строку (домен, исполняемый файл, командную строку), которую вы хотите отфильтровать.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Чувствительно к регистру</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Вы можете указать несколько портов, используя регулярные выражения:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 или 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 или 5551, 5552, 5553, итд:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>до перезагрузки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>К этому списку доменов</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Выберите каталог со списками доменов, которые нужно заблокировать или разрешить.&lt;/p&gt;&lt;p&gt;Поместите в этот каталог файлы с любым расширением, содержащие списки доменов.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Формат каждой записи списка следующий (формат хостов):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;или &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Отказ мгновенно разорвёт соединение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Отклонение приведет к разрыву соединения и уничтожению сокета, который его инициировал</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Отклонить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>Разрешить разрешит соединения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Приложения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>Регулярное выражение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>Из этого PIDа</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation>регулярное выражение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Сеть</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation>Список доменов/IP-адресов</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation>К этому списку сетевых диапазонов</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation>К этому списку IP-адресов</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>К этому списку доменов 
+(регулярные выражения)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation>Ещё</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="83"/>
+        <source>Name (leave blank to autocreate)</source>
+        <translation type="obsolete">Имя (при отсутствии создаётся автоматически)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation>ICMP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation>ICMP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation>SCTP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation>SCTP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation>Сетевой интерфейс</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>Не логировать соединения соотвтсвующие этому правилу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation>Не логировать соединения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="937"/>
+        <source>Color</source>
+        <translation type="obsolete">Цвет</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation>Описание...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation>От этого IP или сети</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation>От этого порта</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>Сетевая статистика OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="284"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">Сохранить в CSV.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="294"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Создать новое правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Состояние</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Начать или остановить перехват</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>События</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Фильтр</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>Разрешить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Запретить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Например: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Узлы</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(дважды щелкните столбец Адреса, чтобы просмотреть сведения об узле)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>включить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">искать название правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Правила приложений</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Постоянно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Временно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Хосты</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(дважды щелкните, чтобы просмотреть сведения об элементе)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Приложения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Адреса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Порты</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Пользователи</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Соединения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Сброшено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Время работы</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Версия</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Удалить все перехваченные события</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Редактировать правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Удалить правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Удалить всю информацию о перехваченных хостах</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Удалить всю информацию о перехваченных приложениях</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Удалить всю информацию о перехваченных адресах</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Удалить всю информацию о перехваченных портах</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Удалить всю информацию о перехваченных пользователях</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(дважды щелкните строку, чтобы просмотреть сведения о правиле)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="665"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Удалить подключения, соответствующие этому правилу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Все приложения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Отклонить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation>Удалить этот узел</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation>Показать настройки этого узла</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation>Запустить или остановить перехват этого узла</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation>Системные правила</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>Статистика</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Помощь</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Закрыть</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Включить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Выключить</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation>Конфигурация применена.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation>Ошибка: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation>Применить изменения...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>Ошибка получения политики цепочки INPUT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>Ошибка получения политики цепочки OUTPUT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>Чтобы настроить правила фаервола из графического интерфейса, нам нужно использовать &apos;nftables&apos; вместо &apos;iptables&apos;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation>Включение фаервола...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation>Выключение фервола...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Порт назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Исходный порт</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>IP-адрес назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>Исходный IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>Входной интерфейс</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>Выходной интерфейс</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>Установить метку соединения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>Совпадение с меткой conntrack</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>Совпадение с состоянием соединения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>Установить метку на пакете</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>Совпадение информации о пакете</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>Квоты пропускной способности</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>Соединения с ограничением скорости</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>Ваша версия protobuf несовместима, необходимо установить protobuf 3.8.0 или выше
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation>Правило удалено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation>Правило добавлено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation>Удаляем правило, подождите</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation>Не удалось обновить правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation>Добавляем правило, подождите</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation>Равно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation>Не равно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation>Больше или равно чем</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation>Больше чем</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation>Меньше или равно чем</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation>Меньше чем</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation>Правило фаервола</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation>Просто</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation>Сложно</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation>Это правило пока не поддерживается.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation>Исключить сервис</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>Разрешить входящие подключения к выбранному порту.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>Разрешить исходящие подключения к выбранному порту.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>значение не может быть 0 или пустым.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>формат значения: 1024/kbytes (или bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>формат значения: 1024/kbytes/секунд (или bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>ограничение скорости недействительно, используйте: bytes, kbytes, mbytes или gbytes.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>ограничение по времени недействительно, используйте: секунды, минуты, часы или дни</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation>порт недействителен.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation>Информация</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation>Ошибка</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation>Предупреждение</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>Системные уведомления недоступны, нужно установить python3-notify2.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>Разрешить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Запретить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>навсегда</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Исходящее соединение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>Процесс запущен из:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>из этой командной строки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>из этого исполняемого файла</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>до перезагрузки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>в порт {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>в {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>от пользователя {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>в {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>в *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">в *{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Удаленный&lt;/b&gt; процесс %s запущенный на &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>подключается к &lt;b&gt;%s&lt;/b&gt; через %s порт %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>пытается разрешить &lt;b&gt;%s&lt;/b&gt; через %s, %s порт %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation>Новое исходящее соединение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>из этого PIDа</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Отклонять</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Адрес сервера не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Конфигурация применена.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Конфигурация сохранения исключений: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>Применение конфигурации к {0}...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>Ошибка при загрузке конфигурации {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Ошибка применения конфигурации: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Предупреждение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Вы должны выбрать файл для базы данных&lt;br&gt;или выбрать тип &quot;В памяти&quot;.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>Тип БД изменен</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Перезапустите графический интерфейс, чтобы эффекты вступили в силу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Наведите указатель мыши на текст, чтобы отобразить справку&lt;br&gt;&lt;br&gt;Не забудьте посетить вики: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation>Система</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>Темы недоступны. Установите qt-material: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation>Тема оформления изменена</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Перезапустите графический интерфейс, чтобы изменить тему оформления</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>Нет подключенных узлов</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation>Хорошо</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="472"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation type="obsolete">Перезапустите графический интерфейс, чтобы изменения вступили в силу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>Исключение сохранения конфигурации узла {0}: {1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;Ошибка при загрузке информации о процессе:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;Ошибка при остановке мониторинга процесса:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>загрузка...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Нет подключенных узлов.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Правило применено.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>протокол не может быть пустым, или снимите галочку</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>Ошибка регулярного выражения протокола</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>путь процесса не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>Ошибка регулярного выражения пути процесса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>командная строка не может быть пустой</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Ошибка регулярного выражения командной строки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Порт назначения не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Ошибка регулярного выражения порта назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Хост назначения не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Ошибка регулярного выражения хоста назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Целевой IP/сеть не могут быть пустыми</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Ошибка регулярного выражения Целевой IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Укажите идентификатор пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Ошибка регулярного выражения ID пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Ошибка применения правила: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Поле списков не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Поле списков должно быть каталогом</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Правило не поддерживается&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Ошибка загрузки правила&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Правило с таким названием уже существует.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>Поле PID не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>Ошибка регулярного выражения поля PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>Выберите хотя бы одно поле.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation>Название сетевого интерфейса не может быть пустым</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation>Ошибка регулярного выражения сетевого интерфейса</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Не запущено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Отключено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Запущено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Вы собираетесь удалить это правило.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Вы уверены?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch статистика сети {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch статистика сети для {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Попадания</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>Сохранить как CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Удалить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Отключить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Включить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Дублировать</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Редактировать</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Правило не найдено по этому имени и узлу</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Ошибка:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Предупреждение:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>Разрешить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Запретить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Всегда</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>До перезагрузки</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Вы собираетесь удалить это правило.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">Última Conexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Имя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Адрес</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Состояние</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Имя хоста</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Версия</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Время</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Действие</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Продолжительность</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Узел</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Включено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Посещаемость</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Протокол</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Процесс</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Назначение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Правило</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>ID пользователя</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Последнее соединение</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Аргументы</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>IP назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Хост назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Порт назначения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Подключен новый узел</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Что</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Имя сети</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Время работы</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Приоритет</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Соединения</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Сброшено</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Что</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Применить к</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Отклонить</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Описание</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Командная строка</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation>Экспортировать правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation>Импортиовать правила</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation>Экспортиовать события в CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation>Выйти</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation>Экспортировать</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation>В буфер обмена</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation>На диск</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation>Выберите директорию для экспортирования правил</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    Вы собираетесь удалить эту запись.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    Вы собираетесь удалить этот узел.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Ошибка удаления узла&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation>Ошибка экспорта правил</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>Выберите директорию для импорта (JSON) файлов с правилами</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation>Правила импортированы нормально</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts b/ui/i18n/locales/tr_TR/opensnitch-tr_TR.ts
new file mode 100644 (file)
index 0000000..bb15095
--- /dev/null
@@ -0,0 +1,3408 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="tr">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>Kullanıcı Kimliği</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Şuradan çalıştırıldı&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>TextLabel</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>Kaynak IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>İşlem Kimliği</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>Hedef IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>Hedef Bağlantı Noktası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>bu programdan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>bu komut satırından</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>bu hedef bağlantı noktası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>bu kullanıcı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>bu hedef IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>bir kere</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30sn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1sa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="706"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>sonsuza kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>Reddet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>İzin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>yeniden başlatılana kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>bu işlem kimliğinden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>eylem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>Güvenlik duvarı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Güvenlik duvarı&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>Gelen</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>Giden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>Profil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>Bir bağlantı noktasına gelen bağlantılara izin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>Hizmete izin ver (GELEN)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="397"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>Bir bağlantı noktasına giden bağlantıları araya girmekten hariç tut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="406"/>
+        <source>Allow service (OUT)</source>
+        <translation>Hizmete izin ver (GİDEN)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="426"/>
+        <source>New rule</source>
+        <translation>Yeni kural</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="453"/>
+        <source>xxx</source>
+        <translation type="obsolete">xxx</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="431"/>
+        <source>Close</source>
+        <translation>Kapat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>Güvenlik duvarı kuralı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>Düğüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="34"/>
+        <source>All</source>
+        <translation type="obsolete">Tümü</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>Etkinleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>Açıklama</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>Basit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>Yeni koşul ekle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>Seçilen koşulu kaldır</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="233"/>
+        <source>Direction</source>
+        <translation>Yön</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="248"/>
+        <source>IN</source>
+        <translation>GELEN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="257"/>
+        <source>OUT</source>
+        <translation>GİDEN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="223"/>
+        <source>Action</source>
+        <translation>Eylem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="285"/>
+        <source>ACCEPT</source>
+        <translation>KABUL ET</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="294"/>
+        <source>DROP</source>
+        <translation>BIRAK</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="303"/>
+        <source>REJECT</source>
+        <translation>GERİ ÇEVİR</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="312"/>
+        <source>RETURN</source>
+        <translation>GERİ DÖN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="442"/>
+        <source>Clear</source>
+        <translation>Temizle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="453"/>
+        <source>Delete</source>
+        <translation>Sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="464"/>
+        <source>Save</source>
+        <translation>Kaydet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="475"/>
+        <source>Add</source>
+        <translation>Ekle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="266"/>
+        <source>FORWARD</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="271"/>
+        <source>PREROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="276"/>
+        <source>POSTROUTING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="321"/>
+        <source>QUEUE</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="330"/>
+        <source>DNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="335"/>
+        <source>SNAT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="340"/>
+        <source>REDIRECT</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="359"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>Tercihler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="484"/>
+        <source>UI</source>
+        <translation>Kullanıcı arayüzü</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="54"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Este timeout es la cuenta atrás que aparece cuando se muestra una ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>Default timeout</source>
+        <translation>Öntanımlı zaman aşımı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>Öntanımlı açılır pencere süresi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="866"/>
+        <source>Default duration</source>
+        <translation>Öntanımlı süre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="162"/>
+        <source>Pop-up default action</source>
+        <translation type="obsolete">Acción por defecto de la ventana emergente</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="483"/>
+        <source>Default action</source>
+        <translation type="obsolete">Acción por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>Öntanımlı hedef</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>merkez</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>sağ üst</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>sağ alt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>sol üst</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>sol alt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="167"/>
+        <source>Prompt dialog default position on screen</source>
+        <translation type="obsolete">Posición por defecto</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>programa göre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>komut satırına göre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>hedef bağlantı noktasına göre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>hedef IP'ye göre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>kullanıcı kimliğine göre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="970"/>
+        <source>once</source>
+        <translation>bir kere</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30sn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1sa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="240"/>
+        <source>for this session</source>
+        <translation type="obsolete">durante esta sesión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>sonsuza kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1012"/>
+        <source>deny</source>
+        <translation>reddet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1021"/>
+        <source>allow</source>
+        <translation>izin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="406"/>
+        <source>Disable pop-ups, only display an alert</source>
+        <translation type="obsolete">Açılır pencereleri devre dışı bırak, yalnızca bir uyarı görüntüle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="823"/>
+        <source>Nodes</source>
+        <translation>Düğümler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="829"/>
+        <source>Process monitor method</source>
+        <translation>İşlem izleme yöntemi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="863"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Öntanımlı süre, bağlı bir kullanıcı arayüzü olmadığında gerçekleşecektir.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="988"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Düğümün adresi.&lt;/p&gt;&lt;p&gt;Öntanımlı: unix:///tmp/osui.sock (Unix soketi ise unix:// zorunludur)&lt;/p&gt;&lt;p&gt;Bağlantı noktası ile birlikte bir IP adresi de olabilir: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="991"/>
+        <source>Address</source>
+        <translation>Adres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1131"/>
+        <source>Default log level</source>
+        <translation>Öntanımlı günlük kaydı düzeyi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1039"/>
+        <source>Version</source>
+        <translation>Sürüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="902"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Öntanımlı eylem, bağlı bir kullanıcı arayüzü olmadığında gerçekleşecektir.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="846"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Günlük kayıtlarının yazılacağı günlük dosyası.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout standart çıktıya yazdıracaktır.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="849"/>
+        <source>Log file</source>
+        <translation>Günlük kaydı dosyası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="578"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">Si marcas esta opción, OpenSnitch te preguntará para Aceptar o Denegar conexiones que no tengan un PID asociado por diferentes razones.
+
+La ventana emergente sólo contendrá información relativa a la conexión.
+
+Nota: Estas conexiones no tienen por qué indicar que algo sospechoso está sucediendo. Simplemente
+es que no hemos descubierto el PID (por ejemplo conexiones que no se originan en la máquina, o paquetes en mal estado).</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>Intercept Unknown Connections</source>
+        <translation type="obsolete">Interceptar conexiones desconocidas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="921"/>
+        <source>HostName</source>
+        <translation>Ana makine adı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1090"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="975"/>
+        <source>until restart</source>
+        <translation>yeniden başlatılana kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="980"/>
+        <source>always</source>
+        <translation>her zaman</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1102"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1107"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="879"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>Yapılandırmayı tüm düğümlere uygula</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1146"/>
+        <source>Database</source>
+        <translation>Veri tabanı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1181"/>
+        <source>In memory</source>
+        <translation>Bellekte</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1186"/>
+        <source>File</source>
+        <translation>Dosya</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1482"/>
+        <source>Close</source>
+        <translation>Kapat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1493"/>
+        <source>Apply</source>
+        <translation>Uygula</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1504"/>
+        <source>Save</source>
+        <translation>Kaydet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>yeniden başlatılana kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1200"/>
+        <source>Database type</source>
+        <translation>Veri tabanı türü</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1207"/>
+        <source>Select</source>
+        <translation>Seç</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="83"/>
+        <source>Pop-ups default options</source>
+        <translation type="obsolete">Opciones por defecto de las ventanas emergentes</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="367"/>
+        <source>Pop-ups default position on screen</source>
+        <translation type="obsolete">Posición en pantalla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="102"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The advanced view allows you to apply more filters on a connection&lt;/p&gt;&lt;p&gt;when a pop-up appears.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">La vista avanzada permite filtrar conexiones por más parámetros</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>Öntanımlı olarak gelişmiş görünümü göster</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="653"/>
+        <source>Action</source>
+        <translation>Eylem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;İşaretlenirse, açılır pencereler gelişmiş görünüm etkinken görüntülenecektir.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>Süre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Öntanımlı olarak, yeni bir açılır pencere göründüğünde, en basit haliyle, bağlantıları veya uygulamaları bağlantının bir özelliğine göre (program, bağlantı noktası, IP, vb.) filtreleyebileceksiniz.&lt;/p&gt;&lt;p&gt;Bu seçeneklerle, bağlantıları filtrelemek için birden fazla alan seçebilirsiniz.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>Bağlantıları şuna göre de filtrele:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="362"/>
+        <source>If checked, this field will be checked when a pop-up is displayed</source>
+        <translation type="obsolete">Si lo seleccionas, este campo se usará para filtrar las conexiones</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>Kullanıcı kimliği</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>Hedef bağlantı noktası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>Hedef IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Bu zaman aşımı, bir açılır iletişim kutusu gösterildiğinde gördüğünüz geri sayımdır.&lt;/p&gt;&lt;p&gt;Açılır pencereye yanıt verilmezse, öntanımlı seçenekler uygulanacaktır.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>Gelişmiş görünüm, bağlantıları filtrelemek için birden fazla alanı kolayca seçmenize olanak tanır</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>İşaretlenirse, bir açılır pencere görüntülendiğinde bu alan seçilecektir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Açılır pencere öntanımlı eylemi.&lt;/p&gt;&lt;p&gt;Yeni bir giden bağlantı kurulmak üzereyken, bu eylem öntanımlı olarak seçilecektir, bu nedenle zaman aşımı devreye girerse, uygulanacak seçenek budur.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Bir açılır pencere kullanıcıdan bir bağlantıya izin vermesini veya reddetmesini isterken:&lt;/p&gt;&lt;p&gt;1. yeni giden bağlantılar reddedilir.&lt;/p&gt;&lt;p&gt;2. bilinen bağlantılara kullanıcı tarafından tanımlanan kurallara göre izin verilir veya reddedilir.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="905"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>GUI bağlantısı kesildiğinde öntanımlı eylem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1001"/>
+        <source>Debug invalid connections</source>
+        <translation>Geçersiz bağlantılarda hata ayıkla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>Açılır pencereler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>Öntanımlı seçenekler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>Ekranda öntanımlı konum</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>any temporary rules</source>
+        <translation>herhangi bir geçici kural</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="478"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Bu seçenek seçildiğinde, seçilen sürenin kuralları grafiksel kullanıcı arayüzündeki geçici kurallar listesine eklenmeyecektir.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Geçici kurallar hala geçerli olacaktır ve yeni bir bağlantıya izin vermeniz/reddetmeniz istendiğinde bunları kullanabilirsiniz.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="481"/>
+        <source>Don&apos;t save rules of duration</source>
+        <translation type="obsolete">Süre kurallarını kaydetme</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="463"/>
+        <source>Show events columns</source>
+        <translation type="obsolete">Mostrar columnas de la pestaña Eventos</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="589"/>
+        <source>Time</source>
+        <translation>Zaman</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="669"/>
+        <source>Destination</source>
+        <translation>Hedef</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="637"/>
+        <source>Protocol</source>
+        <translation>İletişim kuralı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Process</source>
+        <translation>İşlem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Rule</source>
+        <translation>Kural</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="621"/>
+        <source>Node</source>
+        <translation>Düğüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="723"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, opensnitch will prompt you to allow or deny connections that don&apos;t have an asocciated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using wireguard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;İşaretlenirse, opensnitch, çoğunlukla kötü durumdaki bağlantılar olmak üzere çeşitli nedenlerden dolayı, atanmış bir işlem kimliğine sahip olmayan bağlantılara izin vermenizi veya reddetmenizi isteyecektir.&lt;/p&gt;&lt;p&gt;Açılır iletişim kutusu yalnızca ağ bağlantısı hakkında bilgi içerecektir.&lt;/p&gt;&lt;p&gt;Yine de, örneğin wireguard kullanarak bir VPN kurarken olduğu gibi, bunların geçerli bağlantılar olduğu bazı durumlar vardır.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="577"/>
+        <source>Events tab columns</source>
+        <translation>Olaylar sekmesi sütunları</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>işlem kimliğine göre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="461"/>
+        <source>Disable pop-ups, only display an notification</source>
+        <translation type="obsolete">Açılır pencereleri devre dışı bırak, yalnızca bir bildirim görüntüle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="496"/>
+        <source>Desktop notifications</source>
+        <translation>Masaüstü bildirimleri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="514"/>
+        <source>Use system notifications</source>
+        <translation>Sistem bildirimlerini kullan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="530"/>
+        <source>Use Qt notifications</source>
+        <translation>Qt bildirimlerini kullan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="559"/>
+        <source>Test</source>
+        <translation>Test</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="716"/>
+        <source>System</source>
+        <translation>Sistem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="708"/>
+        <source>Theme</source>
+        <translation>Tema</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="998"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;İşaretlenirse OpenSnitch, çoğunlukla kötü durumdaki bağlantılar olmak üzere çeşitli nedenlerden dolayı ilişkili bir işlem kimliğine sahip olmayan bağlantılara izin vermenizi veya reddetmenizi isteyecektir.&lt;/p&gt;&lt;p&gt;Açılır iletişim kutusu yalnızca ağ bağlantısı hakkında bilgi içerecektir.&lt;/p&gt;&lt;p&gt;WireGuard kullanarak bir VPN kurarken olduğu gibi, bunların geçerli bağlantılar olduğu bazı senaryolar vardır.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>minutes</source>
+        <translation>dakika</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1326"/>
+        <source>Minutes between events purges</source>
+        <translation>Olay temizlemeleri arasındaki dakikalar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1352"/>
+        <source>days</source>
+        <translation>gün</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1365"/>
+        <source>Maximum days of events to keep</source>
+        <translation>Saklanacak azami etkinlik günü sayısı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>geri çevir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="473"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>Açılır pencereleri devre dışı bırak, yalnızca bir bildirim görüntüle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="695"/>
+        <source>Command line</source>
+        <translation>Komut satırı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Rules</source>
+        <translation>Kurallar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="756"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation>Bu seçenek seçildiğinde, seçilen sürenin kuralları grafiksel arayüzdeki geçici kurallar listesine eklenmeyecektir.
+
+Geçici kurallar geçerli olmaya devam edecektir ve yeni bir bağlantıya izin vermeniz/vermemeniz istendiğinde bunları kullanabilirsiniz.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="761"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>Süre kurallarını kaydetme/silme</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="779"/>
+        <source>30s or less</source>
+        <translation>30sn veya daha az</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="784"/>
+        <source>5m or less</source>
+        <translation>5dak veya daha az</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="789"/>
+        <source>15m or less</source>
+        <translation>15dak veya daha az</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="794"/>
+        <source>30m or less</source>
+        <translation>30dak veya daha az</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="799"/>
+        <source>1h or less</source>
+        <translation>1sa veya daha az</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="724"/>
+        <source>Language</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>İşlem ayrıntıları</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>yükleniyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD: yükleniyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>bellek istatistikleri: yükleniyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>Durum</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>Açık dosyalar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>G/Ç İstatistikleri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>Bellek eşlemeli dosyalar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>Yığın</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>Ortam değişkenleri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>Uygulama işlem kimlikleri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>Bu işlemi izlemeyi başlat veya durdur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>Kapat</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>Kural</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>Düğüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>Kuralı tüm düğümlere uygula</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>Bu komut satırından</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="472"/>
+        <source>From this executable</source>
+        <translation>Bu programdan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>Eylem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="138"/>
+        <source>/path/to/executable, .*/bin/executable[0-9\.]+$, ...</source>
+        <translation type="obsolete">/programın/yolu, .*/bin/program[0-9\.]+$, ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="610"/>
+        <source>To this IP / Network</source>
+        <translation>Bu IP'ye / Ağa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>bir kere</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="102"/>
+        <source>30s</source>
+        <translation type="obsolete">30sn</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="107"/>
+        <source>5m</source>
+        <translation type="obsolete">5dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="112"/>
+        <source>15m</source>
+        <translation type="obsolete">15dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="117"/>
+        <source>30m</source>
+        <translation type="obsolete">30dak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="122"/>
+        <source>1h</source>
+        <translation type="obsolete">1sa</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="230"/>
+        <source>until restart</source>
+        <translation type="obsolete">hasta reiniciar (el servicio)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>her zaman</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="902"/>
+        <source>To this port</source>
+        <translation>Bu bağlantı noktasına</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>Bu kullanıcı kimliğinden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation>Birden fazla etki alanı belirtmek için virgül veya boşluk kullanılmasına izin verilmez. 
+
+Bunun yerine düzenli ifadeler kullanın: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+veya tek bir etki alanı:
+www.gnu.org - yalnızca www.gnu.org ile eşleşir, ftp.gnu.org, www2.gnu.org vb. ile eşleşmez.
+gnu.org - yalnızca gnu.org ile eşleşir, www.gnu.org, ftp.gnu.org vb. ile eşleşmez.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="603"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.etkialani.org, .*\.etkialani.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Yalnızca TCP, UDP veya UDPLITE izin verilir&lt;/p&gt;&lt;p&gt;Düzenli ifade kullanabilirsiniz, örn: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="411"/>
+        <source>UDP</source>
+        <translation type="obsolete">UDP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="416"/>
+        <source>UDPLITE</source>
+        <translation type="obsolete">UDPLITE</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="421"/>
+        <source>TCP6</source>
+        <translation type="obsolete">TCP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="426"/>
+        <source>UDP6</source>
+        <translation type="obsolete">UDP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="431"/>
+        <source>UDPLITE6</source>
+        <translation type="obsolete">UDPLITE6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="760"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>Tek bir IP belirtebilirsiniz:
+- 192.168.1.1
+
+veya düzenli bir ifade:
+- 192\.168\.1\.[0-9]+
+
+birden fazla IP:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+Ayrıca bir alt ağ da belirtebilirsiniz:
+- 192.168.1.0/24
+
+Not: IP veya ağları ayırmak için virgüllere veya boşluklara izin verilmez.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="532"/>
+        <source>LAN</source>
+        <translation type="obsolete">LAN</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="537"/>
+        <source>127.0.0.0/8</source>
+        <translation type="obsolete">127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="542"/>
+        <source>192.168.0.0/24</source>
+        <translation type="obsolete">192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="547"/>
+        <source>192.168.1.0/24</source>
+        <translation type="obsolete">192.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="552"/>
+        <source>192.168.2.0/24</source>
+        <translation type="obsolete">192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="557"/>
+        <source>192.168.0.0/16</source>
+        <translation type="obsolete">192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="562"/>
+        <source>169.254.0.0/16</source>
+        <translation type="obsolete">169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="567"/>
+        <source>172.16.0.0/12</source>
+        <translation type="obsolete">172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="572"/>
+        <source>10.0.0.0/8</source>
+        <translation type="obsolete">10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="577"/>
+        <source>::1/128</source>
+        <translation type="obsolete">::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="582"/>
+        <source>fc00::/7</source>
+        <translation type="obsolete">fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="587"/>
+        <source>ff00::/8</source>
+        <translation type="obsolete">ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="592"/>
+        <source>fe80::/10</source>
+        <translation type="obsolete">fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/>
+        <source>fd00::/8</source>
+        <translation type="obsolete">fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>Süre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="633"/>
+        <source>Protocol</source>
+        <translation>İletişim kuralı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="750"/>
+        <source>To this host</source>
+        <translation>Bu ana makineye</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>Reddet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>İzin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation type="unfinished">Ad</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>Etkinleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>Kurallar alfabetik sıraya göre denetlenir, böylece onları önceliklendirmek için uygun şekilde adlandırabilirsiniz.
+
+000-localhost-izinver
+001-genelyayin-reddet
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="935"/>
+        <source>leave blank to autocreate</source>
+        <translation type="obsolete">otomatik oluşturmak için boş bırakın</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>İşaretlenirse, bu kural diğer kurallara göre öncelikli olacaktır. Bundan sonra başka hiçbir kural denetlenmeyecektir.
+
+Alfabetik sıraya göre denetlendikleri için kuralı önce denetlenecek şekilde adlandırmalısınız. Örneğin:
+
+[x] Öncelik - 000-oncelik-kurali
+[ ] Öncelik - 001-daha-az-oncelik-kurali</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>Öncelik kuralı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1092"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Öntanımlı olarak, kuralların alanı büyük/küçük harfe duyarsızdır, yani bir işlem gOOgle.CoM adresine erişmeye çalışırsa ve .*google.com adresini Reddetmek için bir kuralınız varsa, bağlantı engellenecektir.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Bu kutuyu işaretlerseniz, filtrelemek istediğiniz dizgeyi (etki alanı, program, komut satırı) tam olarak belirtmeniz gerekir.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1095"/>
+        <source>Case-sensitive</source>
+        <translation>Büyük/küçük harfe duyarlı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="686"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Düzenli ifadeler kullanarak birden fazla bağlantı noktası belirtebilirsiniz:&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt; &lt;p&gt; - 53, 80 veya 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt; &lt;p&gt; - 53, 443 veya 5551, 5552, 5553, vs:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>yeniden başlatılana kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="980"/>
+        <source>To this list of domains</source>
+        <translation>Bu etki alanı listesine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="539"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Engellenecek veya izin verilecek etki alanlarının listelerini içeren bir dizin seçin.&lt;/p&gt;&lt;p&gt;Etki alanı listelerini içeren herhangi bir uzantıya sahip dosyaları bu dizinin içine yerleştirin.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Bir listenin her girdisinin biçimi şu şekildedir (hosts dosyası biçimi):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.etkialani.com&lt;/p&gt;&lt;p&gt;veya &lt;/p&gt;&lt;p&gt;0.0.0.0 www.etkialani.com&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>Reddet seçeneği yalnızca bağlantıyı iptal edecektir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>Geri çevir seçeneği bağlantıyı kesecek ve onu başlatan soketi sonlandıracaktır</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>Geri çevir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>İzin ver seçeneği bağlantıya izin verecektir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>Uygulamalar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Bu alanın değeri her zaman program dosyasının mutlak yoludur: /programın/yolu&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Örnekler:&lt;/p&gt;&lt;p&gt;- Basit: /programın/yolu&lt;/p&gt;&lt;p&gt;- Birden çok yol: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Birden fazla program: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- /tmp'den çalıştırmaları reddet/izin ver:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Daha fazla örnek için &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki sayfasını&lt;/a&gt; ziyaret edin veya &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Tartışma forumlarında&lt;/a&gt; sorun.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>Düzenli ifadedir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Bu alan kullanıcı tarafından çalıştırılan komut satırını içerecek ve eşleşecektir.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Kullanıcı komutu yazdıysa, yalnızca komut görünecektir:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Kullanıcı komutun mutlak veya göreceli yolunu yazdıysa, görünecek olan budur:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>Bu işlem kimliğinden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="479"/>
+        <source>is regular expression</source>
+        <translation>düzenli ifadedir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="491"/>
+        <source>Network</source>
+        <translation>Ağ</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>List of domains/IPs</source>
+        <translation>Etki alanlarının/IP'lerin listesi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="938"/>
+        <source>To this list of network ranges</source>
+        <translation>Bu ağ aralıkları listesine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="945"/>
+        <source>To this list of IPs</source>
+        <translation>Bu IP listesine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="971"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Engellenecek veya izin verilecek IP'lerin listesini içeren dosyaların bulunduğu bir dizin seçin:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;vb.&lt;/p&gt;&lt;p&gt;Satır başına bir IP. Boş veya # ile başlayan satırlar dikkate alınmaz.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1006"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Engellenecek veya izin verilecek ağ aralıklarının listesini içeren dosyaların bulunduğu bir dizin seçin:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;vb.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Satır başına bir ağ aralığı. Boş veya # ile başlayan satırlar dikkate alınmaz.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1034"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Engellenecek veya izin verilecek etki alanlarının listelerini içeren bir dizin seçin.&lt;/p&gt;&lt;p&gt;Etki alanı listelerini içeren herhangi bir uzantıya sahip dosyaları bu dizinin içine yerleştirin.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;Bir listenin her girdisinin biçimi şu şekildedir (hosts dosyası biçimi):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.etkialani.com&lt;/p&gt;&lt;p&gt;veya &lt;/p&gt;&lt;p&gt;0.0.0.0 www.etkialani.com&lt;/p&gt;&lt;p&gt;Boş veya # ile başlayan satırlar dikkate alınmaz.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1049"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>Bu etki alanları listesine
+(düzenli ifadeler)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1076"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Engellenecek veya izin verilecek etki alanlarının düzenli ifadelerini içeren dosyaları içeren bir dizin seçin:&lt;/p&gt;&lt;p&gt;.*\.ornek\.com&lt;/p&gt;&lt;p&gt;Bir etki alanını olduğu gibi de kullanabilirsiniz: &amp;quot;ornek.com&amp;quot;, bu herhangibirsey.ornek.com, herhangibirsey.ornek.com.localdomain, vb. ile eşleşecektir.&lt;/p&gt;&lt;p&gt;Satır başına bir etki alanı. Boş veya # ile başlayan satırlar dikkate alınmaz.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>More</source>
+        <translation>Daha fazla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="83"/>
+        <source>Name (leave blank to autocreate)</source>
+        <translation type="obsolete">Ad (otomatik oluşturmak için boş bırakın)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="566"/>
+        <source>ICMP</source>
+        <translation>ICMP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="571"/>
+        <source>ICMP6</source>
+        <translation>ICMP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="576"/>
+        <source>SCTP</source>
+        <translation>SCTP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="581"/>
+        <source>SCTP6</source>
+        <translation>SCTP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="863"/>
+        <source>Network interface</source>
+        <translation>Ağ arayüzü</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1102"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>Bu kuralla eşleşen bağlantıları günlüğe kaydetme</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1105"/>
+        <source>Don&apos;t log connections</source>
+        <translation>Bağlantıları günlüğe kaydetme</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="937"/>
+        <source>Color</source>
+        <translation type="obsolete">Renk</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1151"/>
+        <source>Description...</source>
+        <translation>Açıklama...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="743"/>
+        <source>From this IP / Network</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="872"/>
+        <source>From this port</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="918"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch Ağ İstatistikleri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="284"/>
+        <source>Save to CSV</source>
+        <translation type="obsolete">CSV olarak kaydet.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="294"/>
+        <source>Ctrl+S</source>
+        <translation type="obsolete">Ctrl+S</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>Yeni bir kural oluştur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;ana makine adı - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>Durum</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1793"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>Araya girmeyi başlat veya durdur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>Olaylar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>Filtrele</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>İzin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>Reddet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>Örn: firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="826"/>
+        <source>Nodes</source>
+        <translation>Düğümler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="554"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Addr column to view details of a node)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(bir düğümün ayrıntılarını görüntülemek için Adres sütununa çift tıklayın)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1700"/>
+        <source>Rules</source>
+        <translation>Kurallar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="995"/>
+        <source>enable</source>
+        <translation>etkinleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="684"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on the Name column to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">(doble click en la columna Nombre para ver los detalles)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="692"/>
+        <source>search rule name</source>
+        <translation type="obsolete">kural adı ara</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="782"/>
+        <source>Application rules</source>
+        <translation>Uygulama kuralları</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="936"/>
+        <source>Permanent</source>
+        <translation>Kalıcı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="945"/>
+        <source>Temporary</source>
+        <translation>Geçici</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1063"/>
+        <source>Hosts</source>
+        <translation>Ana makineler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1364"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click to view details of an item)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(bir ögenin ayrıntılarını görüntülemek için çift tıklayın)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1153"/>
+        <source>Applications</source>
+        <translation>Uygulamalar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1266"/>
+        <source>Addresses</source>
+        <translation>Adresler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1356"/>
+        <source>Ports</source>
+        <translation>Bağlantı noktaları</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1440"/>
+        <source>Users</source>
+        <translation>Kullanıcılar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1544"/>
+        <source>Connections</source>
+        <translation>Bağlantılar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1596"/>
+        <source>Dropped</source>
+        <translation>Bırakıldı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1648"/>
+        <source>Uptime</source>
+        <translation>Çalışma süresi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1767"/>
+        <source>Version</source>
+        <translation>Sürüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>Araya girilen tüm olayları sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1022"/>
+        <source>Edit rule</source>
+        <translation>Kuralı düzenle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1039"/>
+        <source>Delete rule</source>
+        <translation>Kuralı sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="926"/>
+        <source>Delete all intercepted hosts</source>
+        <translation type="obsolete">Araya girilen tüm ana makineleri sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1051"/>
+        <source>Delete all intercepted applications</source>
+        <translation type="obsolete">Araya girilen tüm uygulamaları sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1159"/>
+        <source>Delete all intercepted addresses</source>
+        <translation type="obsolete">Araya girilen tüm adresleri sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1261"/>
+        <source>Delete all intercepted ports</source>
+        <translation type="obsolete">Araya girilen tüm bağlantı noktalarını sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Delete all intercepted users</source>
+        <translation type="obsolete">Araya girilen tüm kullanıcıları sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="699"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(double click on a row to view details of a rule)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation type="obsolete">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:7pt;&quot;&gt;(bir kuralın ayrıntılarını görüntülemek için bir satıra çift tıklayın)&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="665"/>
+        <source>Delete connections that matched this rule</source>
+        <translation type="obsolete">Bu kuralla eşleşen bağlantıları sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="927"/>
+        <source>All applications</source>
+        <translation>Tüm uygulamalar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>Geri çevir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="637"/>
+        <source>Delete this node</source>
+        <translation>Bu düğümü sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="653"/>
+        <source>Show the preferences of this node</source>
+        <translation>Bu düğümün tercihlerini göster</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="669"/>
+        <source>Start or stop interception of this node</source>
+        <translation>Bu düğümün araya girmesini başlat veya durdur</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="777"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="954"/>
+        <source>System rules</source>
+        <translation>Sistem kuralları</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="47"/>
+        <source>Statistics</source>
+        <translation>İstatistikler</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Help</source>
+        <translation>Yardım</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Close</source>
+        <translation>Kapat</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Enable</source>
+        <translation>Etkinleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Disable</source>
+        <translation>Devre dışı bırak</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="91"/>
+        <source>Configuration applied.</source>
+        <translation>Yapılandırma uygulandı.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="404"/>
+        <source>Error: {0}</source>
+        <translation>Hata: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="193"/>
+        <source>Applying changes...</source>
+        <translation>Değişiklikler uygulanıyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="230"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>INPUT zinciri politikası alınırken hata oluştu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="237"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>OUTPUT zinciri politikası alınırken hata oluştu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="290"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>Grafiksel arayüzden güvenlik duvarı kurallarını yapılandırmak için 'iptables' yerine 'nftables' kullanmamız gerekir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="304"/>
+        <source>Enabling firewall...</source>
+        <translation>Güvenlik duvarı etkinleştiriliyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="306"/>
+        <source>Disabling firewall...</source>
+        <translation>Güvenlik duvarı devre dışı bırakılıyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>Hedef Bağlantı Noktası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>Kaynak Bağlantı Noktası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>Hedef IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>Kaynak IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>Giriş arayüzü</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>Çıkış arayüzü</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>conntrack işaretini ayarla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>conntrack işaretini eşleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>conntrack durum(lar)ını eşleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>Paket üzerinde işareti ayarla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>Paket bilgilerini eşleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>Bant genişliği kotaları</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>Bağlantı hızlarını sınırla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="373"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>protobuf sürümünüz uyumsuz, protobuf 3.8.0 veya üstünü kurmanız gerekiyor
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="397"/>
+        <source>Rule deleted</source>
+        <translation>Kural silindi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="401"/>
+        <source>Rule added</source>
+        <translation>Kural eklendi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="420"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation>Birden fazla bağlantı noktası/IP veya aralık/değer belirtmek için ',' veya '-' kullanabilirsiniz:&lt;br&gt;&lt;br&gt;bağlantı noktaları: 22 veya 22,443 veya 50000-60000&lt;br&gt;IP adresleri: 192.168.1.1 veya 192.168.1.30-192.168 .1.130&lt;br&gt;Değerler: echo-reply,echo-request&lt;br&gt;Değerler: new,established,related</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="440"/>
+        <source>Deleting rule, wait</source>
+        <translation>Kural siliniyor, bekleyin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="443"/>
+        <source>Error updating rule</source>
+        <translation>Kural güncellenirken hata oluştu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="483"/>
+        <source>Adding rule, wait</source>
+        <translation>Kural ekleniyor, bekleyin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="492"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation>&lt;bir ifade seçin&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="787"/>
+        <source>Equal</source>
+        <translation>Eşit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="788"/>
+        <source>Not equal</source>
+        <translation>Eşit değil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="789"/>
+        <source>Greater or equal than</source>
+        <translation>Büyük veya eşit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="790"/>
+        <source>Greater than</source>
+        <translation>Büyük</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="791"/>
+        <source>Less or equal than</source>
+        <translation>Küçük veya eşit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="792"/>
+        <source>Less than</source>
+        <translation>Küçük</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1350"/>
+        <source>Firewall rule</source>
+        <translation>Güvenlik duvarı kuralı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="885"/>
+        <source>Simple</source>
+        <translation>Basit</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="890"/>
+        <source>Advanced</source>
+        <translation>Gelişmiş</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1046"/>
+        <source>This rule is not supported yet.</source>
+        <translation>Bu kural henüz desteklenmiyor.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1111"/>
+        <source>Exclude service</source>
+        <translation>Hizmeti hariç tut</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1123"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>Seçilen bağlantı noktasına gelen bağlantılara izin ver.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1125"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>Seçilen bağlantı noktasına giden bağlantılara izin ver.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1201"/>
+        <source>select a statement.</source>
+        <translation>bir ifade seçin.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1217"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>değer 0 veya boş olamaz.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1229"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>değer biçimi 1024/kbayttır (veya bayt, mbayt, gbayt)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1240"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>değer biçimi 1024/kbayt/saniyedir (veya bayt, mbayt, gbayt)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1243"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>hız sınırı geçerli değil, şunları kullanın: bayt, kbayt, mbayt veya gbayt.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1245"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>zaman sınırı geçerli değil, şunları kullanın: saniye, dakika, saat veya gün</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1293"/>
+        <source>port not valid.</source>
+        <translation>bağlantı noktası geçerli değil.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="154"/>
+        <source>Match output interface. Regular expressions not allowed.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="161"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="171"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="178"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="193"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="213"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="221"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="234"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="247"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="254"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="286"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="607"/>
+        <source>num</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="621"/>
+        <source>to</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>menu_close</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="131"/>
+        <source>Close</source>
+        <translation type="obsolete">Cerrar</translation>
+    </message>
+</context>
+<context>
+    <name>menu_help</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="126"/>
+        <source>Help</source>
+        <translation type="obsolete">Ayuda</translation>
+    </message>
+</context>
+<context>
+    <name>menu_statistics</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="120"/>
+        <source>Statistics</source>
+        <translation type="obsolete">Eventos</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="281"/>
+        <source>Info</source>
+        <translation>Bilgi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="285"/>
+        <source>Error</source>
+        <translation>Hata</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="289"/>
+        <source>Warning</source>
+        <translation>Uyarı</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="654"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>Sistem bildirimleri kullanılamıyor, python3-notify2 kurmanız gerekiyor.</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="115"/>
+        <source>Allow</source>
+        <translation>İzin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="114"/>
+        <source>Deny</source>
+        <translation>Reddet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>forever</source>
+        <translation>sonsuza kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="331"/>
+        <source>Outgoing connection</source>
+        <translation>Giden bağlantı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="336"/>
+        <source>Process launched from:</source>
+        <translation>İşlem şuradan başlatıldı:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this command line</source>
+        <translation>bu komut satırından</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="373"/>
+        <source>from this executable</source>
+        <translation>bu programdan</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="208"/>
+        <source>Unknown process</source>
+        <translation type="obsolete">Proceso no encontrado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="50"/>
+        <source>until reboot</source>
+        <translation>yeniden başlatılana kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="379"/>
+        <source>to port {0}</source>
+        <translation>{0} bağlantı noktasına</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="222"/>
+        <source>&lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">&lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="228"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="442"/>
+        <source>to {0}</source>
+        <translation>{0} hedefine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="382"/>
+        <source>from user {0}</source>
+        <translation>{0} kullanıcısından</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="399"/>
+        <source>to {0}.*</source>
+        <translation>{0}.* hedefine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="452"/>
+        <source>to *.{0}</source>
+        <translation>*.{0} hedefine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="378"/>
+        <source>to *{0}</source>
+        <translation type="obsolete">*{0} hedefine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="486"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>%s &lt;b&gt;uzak&lt;/b&gt; işlemi, &lt;b&gt;%s&lt;/b&gt; üzerinde çalışıyor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>&lt;b&gt;%s&lt;/b&gt; hedefine bağlanıyor, %s bağlantı noktası %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="502"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>&lt;b&gt;%s&lt;/b&gt; çözümlemeye çalışıyor, %s aracılığıyla, %s bağlantı noktası %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="122"/>
+        <source>New outgoing connection</source>
+        <translation>Yeni giden bağlantı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from this PID</source>
+        <translation>bu işlem kimliğinden</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="116"/>
+        <source>Reject</source>
+        <translation>Geri çevir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="497"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>&lt;b&gt;%s&lt;/b&gt; hedefine bağlanıyor, %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>popups2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="254"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process &lt;b&gt;%s&lt;/b&gt; running on &lt;b&gt;%s&lt;/b&gt; is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation type="obsolete">El proceso &lt;b&gt;remoto %s&lt;/b&gt; ejecutándose en &lt;b&gt;%s&lt;/b&gt; está conectándose a &lt;b&gt;%s&lt;/b&gt; en el puerto %s %d</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="171"/>
+        <source>Exception saving config: %s</source>
+        <translation type="obsolete">Error al guarda la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="177"/>
+        <source>Applying configuration on %s ...</source>
+        <translation type="obsolete">Aplicando configuración en %s ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="292"/>
+        <source>Server address can not be empty</source>
+        <translation>Sunucu adresi boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="227"/>
+        <source>Error loading %s configuration</source>
+        <translation type="obsolete">Error al cargar la configuración %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="568"/>
+        <source>Configuration applied.</source>
+        <translation>Yapılandırma uygulandı.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="257"/>
+        <source>Error applying configuration: %s</source>
+        <translation type="obsolete">Error al aplicar la configuración: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="386"/>
+        <source>Exception saving config: {0}</source>
+        <translation>Yapılandırma kaydedilirken istisna oluştu: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="500"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>{0} üzerinde yapılandırma uygulanıyor...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="321"/>
+        <source>Error loading {0} configuration</source>
+        <translation>{0} yapılandırması yüklenirken hata oluştu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="570"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>Yapılandırma uygulanırken hata oluştu: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>Warning</source>
+        <translation>Uyarı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="410"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>Veri tabanı için bir dosya seçmelisiniz&lt;br&gt;veya &quot;Bellekte&quot; türünü seçmelisiniz.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="401"/>
+        <source>DB type changed</source>
+        <translation>V.T. türü değişti</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="38"/>
+        <source>Restart the GUI in order effects to take effect</source>
+        <translation>Değişikliklerin etkili olabilmesi için grafiksel arayüzü yeniden başlatın</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>Yardımı görüntülemek için fareyi metinlerin üzerine getirin&lt;br&gt;&lt;br&gt;Wiki sayfasını ziyaret etmeyi unutmayın: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="466"/>
+        <source>System</source>
+        <translation>Sistem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="187"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>Temalar kullanılamıyor. qt-material kurun: pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>UI theme changed</source>
+        <translation>Kullanıcı arayüzü teması değiştirildi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="467"/>
+        <source>Restart the GUI in order to apply the new theme</source>
+        <translation>Yeni temayı uygulamak için grafiksel arayüzü yeniden başlatın</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="388"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>Bağlı düğüm yok</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="508"/>
+        <source>Ok</source>
+        <translation>Tamam</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="472"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation type="obsolete">Değişikliklerin etkili olması için grafiksel arayüzü yeniden başlatın</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="520"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>{0} düğüm yapılandırması kaydedilirken hata oluştu: {1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="164"/>
+        <source>System default</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="433"/>
+        <source>Language changed</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;İşlem bilgileri yüklenirken hata oluştu:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;İşlemin izlenmesi durdurulurken hata oluştu:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>yükleniyor...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="228"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>Bağlı düğüm yok.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="271"/>
+        <source>Rule applied.</source>
+        <translation>Kural uygulandı.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="123"/>
+        <source>Error applying rule: %s</source>
+        <translation type="obsolete">Error al aplicar la regla: %s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="641"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>iletişim kuralı boş olamaz veya işaretini kaldırın</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="655"/>
+        <source>Protocol regexp error</source>
+        <translation>İletişim kuralı düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="659"/>
+        <source>process path can not be empty</source>
+        <translation>işlem yolu boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="673"/>
+        <source>Process path regexp error</source>
+        <translation>İşlem yolu düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="677"/>
+        <source>command line can not be empty</source>
+        <translation>komut satırı boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="691"/>
+        <source>Command line regexp error</source>
+        <translation>Komut satırı düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="731"/>
+        <source>Dest port can not be empty</source>
+        <translation>Hedef bağlantı noktası boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="745"/>
+        <source>Dst port regexp error</source>
+        <translation>Hedef bağlantı noktası düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="749"/>
+        <source>Dest host can not be empty</source>
+        <translation>Hedef ana makine boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="763"/>
+        <source>Dst host regexp error</source>
+        <translation>Hedef ana makine düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="805"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>Hedef IP/Ağ boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="831"/>
+        <source>Dst IP regexp error</source>
+        <translation>Hedef IP düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="843"/>
+        <source>User ID can not be empty</source>
+        <translation>Kullanıcı kimliği boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="857"/>
+        <source>User ID regexp error</source>
+        <translation>Kullanıcı kimliği düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="273"/>
+        <source>Error applying rule: {0}</source>
+        <translation>Kural uygulanırken hata oluştu: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="931"/>
+        <source>Lists field cannot be empty</source>
+        <translation>Listeler alanı boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="933"/>
+        <source>Lists field must be a directory</source>
+        <translation>Listeler alanı bir dizin olmalıdır</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="976"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Kural desteklenmiyor&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="539"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;Kural yüklenirken hata oluştu&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="245"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>Bu ada sahip bir kural zaten var.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="861"/>
+        <source>PID field can not be empty</source>
+        <translation>İşlem kimliği alanı boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="875"/>
+        <source>PID field regexp error</source>
+        <translation>İşlem kimliği alanı düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="963"/>
+        <source>Select at least one field.</source>
+        <translation>En az bir alan seçin.</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="695"/>
+        <source>Network interface can not be empty</source>
+        <translation>Ağ arayüzü boş olamaz</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="709"/>
+        <source>Network interface regexp error</source>
+        <translation>Ağ arayüzü düzenli ifade hatası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="713"/>
+        <source>Source port can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="727"/>
+        <source>Source port regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="767"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="793"/>
+        <source>Source IP regexp error</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Not running</source>
+        <translation>Çalışmıyor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Disabled</source>
+        <translation>Devre dışı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="315"/>
+        <source>Running</source>
+        <translation>Çalışıyor</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="412"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de OpenSnitch</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="414"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1189"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    Bu kuralı silmek üzeresiniz.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    Are you sure?</source>
+        <translation>    Emin misiniz?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="636"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch Ağ İstatistikleri {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="638"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>{0} için OpenSnitch Ağ İstatistikleri</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>Rules</source>
+        <translation type="unfinished">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation type="unfinished">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>Kullanıldı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2581"/>
+        <source>Save as CSV</source>
+        <translation>CSV olarak kaydet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="961"/>
+        <source>Delete</source>
+        <translation>Sil</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="948"/>
+        <source>always</source>
+        <translation type="obsolete">siempre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="580"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</source>
+        <translation type="obsolete">&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="954"/>
+        <source>Disable</source>
+        <translation>Devre dışı bırak</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Enable</source>
+        <translation>Etkinleştir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="959"/>
+        <source>Duplicate</source>
+        <translation>Çoğalt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="960"/>
+        <source>Edit</source>
+        <translation>Düzenle</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1248"/>
+        <source>Rule not found by that name and node</source>
+        <translation>Bu ada ve düğüme göre kural bulunamadı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1301"/>
+        <source>&lt;b&gt;Error:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Hata:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1308"/>
+        <source>Warning:</source>
+        <translation>Uyarı:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="940"/>
+        <source>Allow</source>
+        <translation>İzin ver</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Deny</source>
+        <translation>Reddet</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="945"/>
+        <source>Always</source>
+        <translation>Her zaman</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="946"/>
+        <source>Until reboot</source>
+        <translation>Yeniden başlatılana kadar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1711"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    Bu kuralı silmek üzeresiniz.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <translation type="obsolete">Última Conexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>xxxxx</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nombre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Dirección</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Estado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hostname</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Versión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Reglas</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Hora</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Acción</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Duración</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Nodo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Habilitado</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Total</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Protocolo</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Aplicación</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Destino</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">Regla</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">UserID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces</comment>
+        <translation type="obsolete">ÚltimaConexión</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ad</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Adres</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Durum</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ana makine adı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="423"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Sürüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kurallar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Zaman</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Eylem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Süre</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Düğüm</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Etkin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="438"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kullanıldı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>İletişim kuralı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>İşlem</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Hedef</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Kural</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>KullanıcıKimliği</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="311"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>SonBağlantı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Args</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation type="obsolete">Argümanlar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>HedefIP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>HedefAnaMakine</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>HedefBağlantıNoktası</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="776"/>
+        <source>New node connected</source>
+        <translation>Yeni düğüm bağlandı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>Ne</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>Ağ adı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="291"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Çalışma süresi</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="300"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Öncelik</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Bağlantılar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Bırakıldı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Ne</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="931"/>
+        <source>Apply to</source>
+        <translation>Uygula</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Reject</source>
+        <translation>Geri çevir</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Açıklama</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>Komut satırı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Export rules</source>
+        <translation>Kuralları dışa aktar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Import rules</source>
+        <translation>Kuralları içe aktar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Export events to CSV</source>
+        <translation>Olayları CSV dosyasına aktar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>Quit</source>
+        <translation>Çıkış</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="932"/>
+        <source>Export</source>
+        <translation>Dışa aktar</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>To clipboard</source>
+        <translation>Panoya</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="965"/>
+        <source>To disk</source>
+        <translation>Diske</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2523"/>
+        <source>Select a directory to export rules</source>
+        <translation>Kuralları dışa aktarmak için bir dizin seçin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1191"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    Bu girdiyi silmek üzeresiniz.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1678"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    Bu düğümü silmek üzeresiniz.    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1687"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;Düğüm silinirken hata oluştu&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2478"/>
+        <source>Error exporting rules</source>
+        <translation>Kuralları dışa aktarırken hata oluştu</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2552"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>İçe aktarılacak kuralları (JSON dosyaları) içeren bir dizin seçin</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2566"/>
+        <source>Rules imported fine</source>
+        <translation>Kurallar başarıyla içe aktarıldı</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="211"/>
+        <source>WARNING</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Details</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="835"/>
+        <source>New</source>
+        <translation type="unfinished"></translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="774"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation type="obsolete">    Estás a punto de borrar esta regla.    </translation>
+    </message>
+</context>
+<context>
+    <name>stats_deleterule2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="776"/>
+        <source>    Are you sure?</source>
+        <translation type="obsolete">    ¿Estás seguro?</translation>
+    </message>
+</context>
+<context>
+    <name>stats_disabled</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="74"/>
+        <source>Disabled</source>
+        <translation type="obsolete">Deshabilitado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_notrunning</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="73"/>
+        <source>Not running</source>
+        <translation type="obsolete">Parado</translation>
+    </message>
+</context>
+<context>
+    <name>stats_running</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="75"/>
+        <source>Running</source>
+        <translation type="obsolete">Interceptando</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="409"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation type="obsolete">Eventos de red OpenSnitch</translation>
+    </message>
+</context>
+<context>
+    <name>stats_wintitle2</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="411"/>
+        <source>OpenSnitch Network Statistics for</source>
+        <translation type="obsolete">Eventos de OpenSnitch de</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/locales/zh_TW/opensnitch-zh_TW.ts b/ui/i18n/locales/zh_TW/opensnitch-zh_TW.ts
new file mode 100644 (file)
index 0000000..4c4582a
--- /dev/null
@@ -0,0 +1,3043 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1">
+<context>
+    <name>Dialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="34"/>
+        <source>opensnitch-qt</source>
+        <translation>opensnitch-qt</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="300"/>
+        <source>User ID</source>
+        <translation>使用者 ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="334"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;執行來自&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="647"/>
+        <source>TextLabel</source>
+        <translation>文字標籤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="437"/>
+        <source>Source IP</source>
+        <translation>來源 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="458"/>
+        <source>Process ID</source>
+        <translation>處理程序 ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="601"/>
+        <source>Destination IP</source>
+        <translation>目標 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="622"/>
+        <source>Dst Port</source>
+        <translation>目標連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="702"/>
+        <source>from this executable</source>
+        <translation>來自此執行檔</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="707"/>
+        <source>from this command line</source>
+        <translation>來自此命令列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="712"/>
+        <source>this destination port</source>
+        <translation>此目標連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="717"/>
+        <source>this user</source>
+        <translation>此使用者</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="722"/>
+        <source>this destination ip</source>
+        <translation>此目標 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="727"/>
+        <source>from this PID</source>
+        <translation>來自此 PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="751"/>
+        <source>once</source>
+        <translation>一次</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="756"/>
+        <source>30s</source>
+        <translation>30 秒</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="761"/>
+        <source>5m</source>
+        <translation>5 分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="766"/>
+        <source>15m</source>
+        <translation>15 分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="771"/>
+        <source>30m</source>
+        <translation>30 分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="776"/>
+        <source>1h</source>
+        <translation>1 小時</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="781"/>
+        <source>until reboot</source>
+        <translation>持續到重新啟動</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="786"/>
+        <source>forever</source>
+        <translation>永久</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="809"/>
+        <source>action</source>
+        <translation>動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="337"/>
+        <source>Allow</source>
+        <translation>允許</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/prompt.ui" line="865"/>
+        <source>+</source>
+        <translation>+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="14"/>
+        <source>Firewall</source>
+        <translation>防火牆</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="55"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;防火牆&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="275"/>
+        <source>Profile</source>
+        <translation>設定檔</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="346"/>
+        <source>Deny</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="313"/>
+        <source>Outbound</source>
+        <translation>對外</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="320"/>
+        <source>Inbound</source>
+        <translation>對內</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="375"/>
+        <source>Allow inbound connections to a port</source>
+        <translation>允許一個連接埠的對內連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="378"/>
+        <source>Allow service (IN)</source>
+        <translation>允許服務 (對內)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="398"/>
+        <source>Exclude outbound connections to a port from being intercepted</source>
+        <translation>排除被攔截到一個連接埠的對外連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="407"/>
+        <source>Allow service (OUT)</source>
+        <translation>允許服務 (對外)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall.ui" line="427"/>
+        <source>New rule</source>
+        <translation>新增規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="421"/>
+        <source>Close</source>
+        <translation>關閉</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="14"/>
+        <source>Firewall rule</source>
+        <translation>防火牆規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="26"/>
+        <source>Node</source>
+        <translation>節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="38"/>
+        <source>Enable</source>
+        <translation>啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="50"/>
+        <source>Description</source>
+        <translation>描述</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="90"/>
+        <source>Simple</source>
+        <translation>簡易</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="154"/>
+        <source>Add new condition</source>
+        <translation>新增條件</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="177"/>
+        <source>Remove selected condition</source>
+        <translation>移除選定的條件</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="221"/>
+        <source>Direction</source>
+        <translation>方向</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="232"/>
+        <source>IN</source>
+        <translation>輸入</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="241"/>
+        <source>OUT</source>
+        <translation>輸出</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="250"/>
+        <source>FORWARD</source>
+        <translation>轉發</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="255"/>
+        <source>PREROUTING</source>
+        <translation>預路由</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="260"/>
+        <source>POSTROUTING</source>
+        <translation>後路由</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="268"/>
+        <source>Action</source>
+        <translation>動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="279"/>
+        <source>ACCEPT</source>
+        <translation>接受</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="288"/>
+        <source>DROP</source>
+        <translation>丟棄</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="297"/>
+        <source>REJECT</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="306"/>
+        <source>RETURN</source>
+        <translation>返回</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="315"/>
+        <source>QUEUE</source>
+        <translation>佇列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="323"/>
+        <source>DNAT</source>
+        <translation>DNAT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="328"/>
+        <source>SNAT</source>
+        <translation>SNAT</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="333"/>
+        <source>REDIRECT</source>
+        <translation>重新導向</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="349"/>
+        <source>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</source>
+        <translation>依據動作(例如:目標),參數的語法將有所不同。
+一些範例:
+
+QUEUE -&gt; num 0(或 1、2、...)
+REDIRECT、TPROXY、DNAT、SNAT、MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048(masquerade)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="432"/>
+        <source>Clear</source>
+        <translation>清除</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="443"/>
+        <source>Delete</source>
+        <translation>刪除</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="454"/>
+        <source>Save</source>
+        <translation>儲存</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/firewall_rule.ui" line="465"/>
+        <source>Add</source>
+        <translation>新增</translation>
+    </message>
+</context>
+<context>
+    <name>PreferencesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="14"/>
+        <source>Preferences</source>
+        <translation>偏好設定</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="39"/>
+        <source>Pop-ups</source>
+        <translation>彈出視窗</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="64"/>
+        <source>Default options</source>
+        <translation>預設選項</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="110"/>
+        <source>If checked, this field will be selected when a pop-up is displayed</source>
+        <translation>如果選取,則在顯示彈出視窗時將選擇此欄位</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="81"/>
+        <source>User ID</source>
+        <translation>使用者 ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="97"/>
+        <source>Destination port</source>
+        <translation>目標連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="113"/>
+        <source>Destination IP</source>
+        <translation>目標 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1173"/>
+        <source>deny</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1182"/>
+        <source>allow</source>
+        <translation>允許</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="147"/>
+        <source>reject</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="159"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. new outgoing connections are denied.&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;彈出視窗的預設動作。&lt;/p&gt;&lt;p&gt;當新的對外連線即將建立時,此動作將被預設選擇,所以如果逾時,這將是被套用的選項。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;當彈出視窗正在詢問使用者是否允許或拒絕連線時:&lt;/p&gt;&lt;p&gt;1. 新的對外連線被拒絕。&lt;/p&gt;&lt;p&gt;2. 已知的連線依據使用者定義的規則被允許或拒絕。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="872"/>
+        <source>Action</source>
+        <translation>動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="179"/>
+        <source>center</source>
+        <translation>中央</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="184"/>
+        <source>top right</source>
+        <translation>右上</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="189"/>
+        <source>bottom right</source>
+        <translation>右下</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="194"/>
+        <source>top left</source>
+        <translation>左上</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="199"/>
+        <source>bottom left</source>
+        <translation>左下</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1205"/>
+        <source>once</source>
+        <translation>一次</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="219"/>
+        <source>30s</source>
+        <translation>30 秒</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="224"/>
+        <source>5m</source>
+        <translation>5 分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="229"/>
+        <source>15m</source>
+        <translation>15 分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="234"/>
+        <source>30m</source>
+        <translation>30 分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="239"/>
+        <source>1h</source>
+        <translation>1 小時</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="244"/>
+        <source>until reboot</source>
+        <translation>持續到重新啟動</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="249"/>
+        <source>forever</source>
+        <translation>永久</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="263"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you&apos;ll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;預設情況下,當出現新的彈出視窗時,以其最簡單的形式,您將能夠按連線的一個屬性(執行檔、連接埠、IP 等)篩選連線或應用程式。&lt;/p&gt;&lt;p&gt;使用這些選項,您可以選擇多個欄位來篩選連線。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="266"/>
+        <source>Filter connections also by:</source>
+        <translation>也按以下方式篩選連線:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="283"/>
+        <source>by executable</source>
+        <translation>依執行檔</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="288"/>
+        <source>by command line</source>
+        <translation>依命令列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="293"/>
+        <source>by destination port</source>
+        <translation>依目標連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="298"/>
+        <source>by destination ip</source>
+        <translation>依目標 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="303"/>
+        <source>by user id</source>
+        <translation>依使用者 ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="308"/>
+        <source>by PID</source>
+        <translation>依 PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="323"/>
+        <source>Default target</source>
+        <translation>預設目標</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="330"/>
+        <source>Default position on screen</source>
+        <translation>在螢幕上的預設位置</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="340"/>
+        <source>Pop-up default duration</source>
+        <translation>彈出視窗的預設持續時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="343"/>
+        <source>Duration</source>
+        <translation>持續時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="356"/>
+        <source>The advanced view allows you to easily select multiple fields to filter connections</source>
+        <translation>進階檢視讓您可以輕鬆選擇多個欄位來篩選連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="359"/>
+        <source>Show advanced view by default</source>
+        <translation>預設顯示進階檢視</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="375"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;如果選取,彈出視窗將會以進階檢視顯示。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="466"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;此逾時是顯示彈出對話框時看到的倒數計時。&lt;/p&gt;&lt;p&gt;如果未回答彈出視窗,將套用預設選項。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="469"/>
+        <source>Default timeout</source>
+        <translation>預設逾時</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="476"/>
+        <source>Disable pop-ups, only display a notification</source>
+        <translation>停用彈出視窗,只顯示通知</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="487"/>
+        <source>UI</source>
+        <translation>使用者介面</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1128"/>
+        <source>General</source>
+        <translation>一般</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="509"/>
+        <source>Language</source>
+        <translation>語言</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="533"/>
+        <source>System</source>
+        <translation>系統</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="547"/>
+        <source>Theme</source>
+        <translation>主題</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="554"/>
+        <source>By default the GUI is started when login</source>
+        <translation>預設登入時啟動圖形介面</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="557"/>
+        <source>Autostart the GUI upon login</source>
+        <translation>登入時自動啟動圖形介面</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="568"/>
+        <source>Server</source>
+        <translation>伺服器</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="581"/>
+        <source>4MiB</source>
+        <translation>4 MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="586"/>
+        <source>8MiB</source>
+        <translation>8 MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="591"/>
+        <source>16MiB</source>
+        <translation>16 MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="596"/>
+        <source>32MiB</source>
+        <translation>32 MiB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="605"/>
+        <source>Simple</source>
+        <translation>簡易</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="610"/>
+        <source>Simple TLS</source>
+        <translation>簡易 TLS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="615"/>
+        <source>Mutual TLS</source>
+        <translation>雙向 TLS</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="623"/>
+        <source>Absolute path to the cert file</source>
+        <translation>憑證檔案的絕對路徑</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="630"/>
+        <source>Maximum size of each message from nodes. Default 4MB</source>
+        <translation>來自節點的每個訊息的最大大小。預設 4MB</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="633"/>
+        <source>Max gRPC channel size</source>
+        <translation>gRPC 頻道的最大大小</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="640"/>
+        <source>Absolute path to the cert key file</source>
+        <translation>憑證金鑰檔案的絕對路徑</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="647"/>
+        <source>&lt;p&gt;Simple: no authentication, TLS simple/mutual: use SSL certificates to authenticate nodes.&lt;/p&gt;&lt;p&gt;Visit the wiki for more information.&lt;/p&gt;</source>
+        <translation>&lt;p&gt;簡易:無驗證,TLS 簡易/雙向:使用 SSL 憑證來驗證節點。&lt;/p&gt;&lt;p&gt;造訪 wiki 以取得更多資訊。&lt;/p&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="650"/>
+        <source>Authentication type</source>
+        <translation>驗證類型</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="657"/>
+        <source>Absolute path to the CA cert file</source>
+        <translation>CA 憑證檔案的絕對路徑</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="673"/>
+        <source>Desktop notifications</source>
+        <translation>桌面通知</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="685"/>
+        <source>Enable</source>
+        <translation>啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="703"/>
+        <source>Use system notifications</source>
+        <translation>使用系統通知</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="719"/>
+        <source>Use Qt notifications</source>
+        <translation>使用 Qt 通知</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="748"/>
+        <source>Test</source>
+        <translation>測試</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="769"/>
+        <source>Events tab columns</source>
+        <translation>事件標籤欄位</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="808"/>
+        <source>Time</source>
+        <translation>時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="824"/>
+        <source>Rule</source>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="840"/>
+        <source>Node</source>
+        <translation>節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="856"/>
+        <source>Protocol</source>
+        <translation>通訊協定</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="888"/>
+        <source>Destination</source>
+        <translation>目標</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="904"/>
+        <source>Process</source>
+        <translation>處理程序</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="914"/>
+        <source>Command line</source>
+        <translation>命令列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="932"/>
+        <source>Rules</source>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="940"/>
+        <source>When this option is selected, the rules of the selected duration won&apos;t be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</source>
+        <translation>選擇此選項時,選定持續時間的規則將不會被新增到 GUI 的臨時規則列表中。
+
+臨時規則仍然有效,並且您可以在提示允許/阻擋新連線時使用它們。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="945"/>
+        <source>Don&apos;t save/Delete rules of duration</source>
+        <translation>不儲存/刪除持續時間的規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="953"/>
+        <source>any temporary rules</source>
+        <translation>任何臨時規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="963"/>
+        <source>30s or less</source>
+        <translation>30 秒或更少</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="968"/>
+        <source>5m or less</source>
+        <translation>5 分鐘或更少</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="973"/>
+        <source>15m or less</source>
+        <translation>15 分鐘或更少</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="978"/>
+        <source>30m or less</source>
+        <translation>30 分鐘或更少</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="983"/>
+        <source>1h or less</source>
+        <translation>1 小時或更少</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1007"/>
+        <source>Nodes</source>
+        <translation>節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1019"/>
+        <source>HostName</source>
+        <translation>主機名稱</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1067"/>
+        <source>Version</source>
+        <translation>版本</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1109"/>
+        <source>Apply configuration to all nodes</source>
+        <translation>將設定套用到所有節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1134"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it&apos;s a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;節點的位址。&lt;/p&gt;&lt;p&gt;預設:unix:///tmp/osui.sock(如果是 Unix socket,unix:// 是必須的)&lt;/p&gt;&lt;p&gt;也可以是帶有連接埠的 IP 位址:127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1137"/>
+        <source>Address</source>
+        <translation>位址</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1148"/>
+        <source>unix:///tmp/osui.sock</source>
+        <translation>unix:///tmp/osui.sock</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1156"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;當沒有連接 UI 時,將進行預設動作。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1159"/>
+        <source>Default action when the GUI is disconnected</source>
+        <translation>GUI 斷開連接時的預設動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1194"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there&apos;s no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;當沒有連接 UI 時,將進行預設持續時間。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1197"/>
+        <source>Default duration</source>
+        <translation>預設的持續時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1210"/>
+        <source>until restart</source>
+        <translation>直到重新啟動</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1215"/>
+        <source>always</source>
+        <translation>永遠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1223"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don&apos;t have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There&apos;re some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;如果選取,OpenSnitch 將提示您允許或拒絕沒有相關 PID 的連線,原因有很多,主要是因為連線狀態不佳。&lt;/p&gt;&lt;p&gt;彈出對話框只會包含有關網路連線的資訊。&lt;/p&gt;&lt;p&gt;儘管在某些情況下,這些是有效的連線,例如使用 WireGuard 建立 VPN。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1226"/>
+        <source>Debug invalid connections</source>
+        <translation>偵錯無效連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1240"/>
+        <source>Process monitor method</source>
+        <translation>處理程序監控方法</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1278"/>
+        <source>Logging</source>
+        <translation>記錄</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1291"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;寫入記錄的日誌檔案。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout 將會將記錄列印到標準輸出。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1294"/>
+        <source>Log file</source>
+        <translation>日誌檔案</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1301"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will log timestamp microseconds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;如果選取,OpenSnitch 將記錄時間戳微秒。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1304"/>
+        <source>Log timestamp microseconds</source>
+        <translation>記錄時間戳微秒</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1348"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will use the UTC timezone for timestamps.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;如果選取,OpenSnitch 將使用 UTC 時區的時間戳。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1351"/>
+        <source>Log UTC timestamps</source>
+        <translation>記錄 UTC 時間戳</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1358"/>
+        <source>Default log level</source>
+        <translation>預設記錄等級</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1369"/>
+        <source>/var/log/opensnitchd.log</source>
+        <translation>/var/log/opensnitchd.log</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1374"/>
+        <source>/dev/stdout</source>
+        <translation>/dev/stdout</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1410"/>
+        <source>Database</source>
+        <translation>資料庫</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1445"/>
+        <source>In memory</source>
+        <translation>在記憶體中</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1450"/>
+        <source>File</source>
+        <translation>檔案</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1464"/>
+        <source>Database type</source>
+        <translation>資料庫類型</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1471"/>
+        <source>Select</source>
+        <translation>選擇</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1558"/>
+        <source>minutes</source>
+        <translation>分鐘</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1590"/>
+        <source>Minutes between events purges</source>
+        <translation>事件清除之間的分鐘數</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1616"/>
+        <source>days</source>
+        <translation>天</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1629"/>
+        <source>Maximum days of events to keep</source>
+        <translation>保留事件的最大天數</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1753"/>
+        <source>Close</source>
+        <translation>關閉</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1764"/>
+        <source>Apply</source>
+        <translation>套用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/preferences.ui" line="1775"/>
+        <source>Save</source>
+        <translation>儲存</translation>
+    </message>
+</context>
+<context>
+    <name>ProcessDetailsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="14"/>
+        <source>Process details</source>
+        <translation>處理程序詳細資訊</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="61"/>
+        <source>loading...</source>
+        <translation>載入中...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="81"/>
+        <source>CWD: loading...</source>
+        <translation>CWD:載入中...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="93"/>
+        <source>mem stats: loading...</source>
+        <translation>記憶體狀態:載入中...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="121"/>
+        <source>Status</source>
+        <translation>狀態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="135"/>
+        <source>Open files</source>
+        <translation>開啟檔案</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="149"/>
+        <source>I/O Statistics</source>
+        <translation>I/O 統計</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="163"/>
+        <source>Memory mapped files</source>
+        <translation>記憶體映射檔案</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="177"/>
+        <source>Stack</source>
+        <translation>堆疊</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="191"/>
+        <source>Environment variables</source>
+        <translation>環境變數</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="210"/>
+        <source>Application pids</source>
+        <translation>應用程式 PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="240"/>
+        <source>Start or stop monitoring this process</source>
+        <translation>開始或停止監控此程序</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/process_details.ui" line="256"/>
+        <source>Close</source>
+        <translation>關閉</translation>
+    </message>
+</context>
+<context>
+    <name>RulesDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="20"/>
+        <source>Rule</source>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="56"/>
+        <source>Action</source>
+        <translation>動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="89"/>
+        <source>Duration</source>
+        <translation>持續時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="97"/>
+        <source>once</source>
+        <translation>一次</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="127"/>
+        <source>until reboot</source>
+        <translation>直到重新啟動</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="132"/>
+        <source>always</source>
+        <translation>永遠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="148"/>
+        <source>Deny will just discard the connection</source>
+        <translation>阻擋將僅丟棄連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="151"/>
+        <source>Deny</source>
+        <translation>阻擋</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="165"/>
+        <source>Reject will drop the connection, and kill the socket that initiated it</source>
+        <translation>拒絕將會丟棄連線,並終止啟動它的socket</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="168"/>
+        <source>Reject</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="185"/>
+        <source>Allow will allow the connection</source>
+        <translation>允許將允許連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="191"/>
+        <source>Allow</source>
+        <translation>允許</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="207"/>
+        <source>Enable</source>
+        <translation>啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="214"/>
+        <source>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it&apos;ll be checked first, because they&apos;re checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</source>
+        <translation>如果選取,此規則將優於其他規則。在此之後不會檢查其他規則。
+
+您必須以這樣的方式命名規則,以便首先檢查它,因為它們按字母順序檢查。例如:
+
+[x] 優先 - 000-priority-rule
+[  ] 優先 - 001-less-priority-rule</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="222"/>
+        <source>Priority rule</source>
+        <translation>優先規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="238"/>
+        <source>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</source>
+        <translation>規則按字母順序檢查,因此您可以相應地命名它們以優先考慮它們。
+
+000-allow-localhost
+001-deny-broadcast
+...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="245"/>
+        <source>Name</source>
+        <translation>名稱</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="300"/>
+        <source>Node</source>
+        <translation>節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="323"/>
+        <source>Apply rule to all nodes</source>
+        <translation>將規則套用到所有節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="346"/>
+        <source>Applications</source>
+        <translation>應用程式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="355"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;此欄位的值始終是執行檔的絕對路徑:/path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;範例:&lt;/p&gt;&lt;p&gt;- 簡易:/path/to/binary&lt;/p&gt;&lt;p&gt;- 多路徑:^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- 多個執行檔:^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$&lt;/p&gt;&lt;p&gt;- 拒絕/允許從 /tmp 執行:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;要取得更多範例,請造訪&lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki 頁面&lt;/a&gt;或在&lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;討論論壇&lt;/a&gt;提問。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="365"/>
+        <source>Is regular expression</source>
+        <translation>是正規表達式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="372"/>
+        <source>From this user ID</source>
+        <translation>來自此使用者 ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="379"/>
+        <source>From this command line</source>
+        <translation>來自此命令列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="389"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;此欄位將包含並符合使用者執行的命令列。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;如果使用者輸入了命令,只會出現該命令:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;如果使用者輸入了命令的絕對或相對路徑,那就會出現該內容:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="399"/>
+        <source>From this PID</source>
+        <translation>來自此 PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="466"/>
+        <source>From this executable</source>
+        <translation>來自此執行檔</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="473"/>
+        <source>is regular expression</source>
+        <translation>是正規表達式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="485"/>
+        <source>Network</source>
+        <translation>網路</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="520"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;僅允許 TCP、UDP 或 UDPLITE&lt;/p&gt;&lt;p&gt;您可以使用正規表達式,例如:^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="526"/>
+        <source>TCP</source>
+        <translation>TCP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="560"/>
+        <source>ICMP</source>
+        <translation>ICMP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="565"/>
+        <source>ICMP6</source>
+        <translation>ICMP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="570"/>
+        <source>SCTP</source>
+        <translation>SCTP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="575"/>
+        <source>SCTP6</source>
+        <translation>SCTP6</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="586"/>
+        <source>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it&apos;ll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it&apos;ll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</source>
+        <translation type="unfinished"></translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="597"/>
+        <source>www.domain.org, .*\.domain.org</source>
+        <translation>www.domain.org, .*\.domain.org</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="604"/>
+        <source>To this IP / Network</source>
+        <translation>到此 IP / 網路</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="627"/>
+        <source>Protocol</source>
+        <translation>通訊協定</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="754"/>
+        <source>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</source>
+        <translation>您可以指定單一 IP:
+- 192.168.1.1
+
+或是正規表達式:
+- 192\.168\.1\.[0-9]+
+
+多個 IP:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+您也可以指定子網:
+- 192.168.1.0/24
+
+注意:不允許使用逗號或空格分隔 IP 或網路。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="659"/>
+        <source>LAN</source>
+        <translation>區域網路</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="664"/>
+        <source>MULTICAST</source>
+        <translation>多播</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="669"/>
+        <source>127.0.0.0/8</source>
+        <translation>127.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="674"/>
+        <source>192.168.0.0/24</source>
+        <translation>192.168.0.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="679"/>
+        <source>192.168.1.0/24</source>
+        <translation>92.168.1.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="684"/>
+        <source>192.168.2.0/24</source>
+        <translation>192.168.2.0/24</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="689"/>
+        <source>192.168.0.0/16</source>
+        <translation>192.168.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="694"/>
+        <source>169.254.0.0/16</source>
+        <translation>169.254.0.0/16</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="699"/>
+        <source>172.16.0.0/12</source>
+        <translation>172.16.0.0/12</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="704"/>
+        <source>10.0.0.0/8</source>
+        <translation>10.0.0.0/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="709"/>
+        <source>::1/128</source>
+        <translation>::1/128</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="714"/>
+        <source>fc00::/7</source>
+        <translation>fc00::/7</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="719"/>
+        <source>ff00::/8</source>
+        <translation>ff00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="724"/>
+        <source>fe80::/10</source>
+        <translation>fe80::/10</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="729"/>
+        <source>fd00::/8</source>
+        <translation>fd00::/8</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="737"/>
+        <source>From this IP / Network</source>
+        <translation>來自此 IP / 網路</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="744"/>
+        <source>To this host</source>
+        <translation>到此主機</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="857"/>
+        <source>Network interface</source>
+        <translation>網路介面</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="866"/>
+        <source>From this port</source>
+        <translation>來自此連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="912"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;您可以使用正規表達式指定多個連接埠:&lt;/p&gt;&lt;p&gt;- 53、80 或 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53、443 或 5551、5552、5553 等:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="896"/>
+        <source>To this port</source>
+        <translation>到此連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="926"/>
+        <source>List of domains/IPs</source>
+        <translation>網域/IP 清單</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="932"/>
+        <source>To this list of network ranges</source>
+        <translation>到此網路範圍清單</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="939"/>
+        <source>To this list of IPs</source>
+        <translation>到此 IP 清單</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="965"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;選擇一個含有要封鎖或允許的 IP 清單的檔案的目錄:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;等等。&lt;/p&gt;&lt;p&gt;每行一個 IP。空行或以 # 開頭的行將被忽略。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="974"/>
+        <source>To this list of domains</source>
+        <translation>到此網域清單</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1000"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;選擇一個含有要封鎖或允許的網路範圍清單的檔案的目錄:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;等等。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;每行一個網路範圍。空行或以 # 開頭的行將被忽略。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1028"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;選擇一個含有要封鎖或允許的網域清單的檔案的目錄。&lt;/p&gt;&lt;p&gt;在該目錄中放置含有網域清單的任何副檔名的檔案。&lt;/p&gt;&lt;p&gt;&lt;br/&gt;每個清單條目的格式如下(hosts 格式):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;或 &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;空行或以 # 開頭的行將被忽略。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1043"/>
+        <source>To this list of domains 
+(regular expressions)</source>
+        <translation>到此網域清單
+(正規表達式)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1070"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it&apos;ll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;請在目錄中選擇一個含有要封鎖或允許的網域正規表達式的檔案:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;您也可以直接使用網域:&amp;quot;example.com&amp;quot;,它將符合 whatever.example.com、whatever.example.com.localdomain 等。&lt;/p&gt;&lt;p&gt;每行輸入一個網域。空行或以 # 開頭的行將被忽略。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1080"/>
+        <source>More</source>
+        <translation>更多</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1086"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;預設情況下,規則的欄位不區分大小寫,即,如果一個程序試圖存取 gOOgle.CoM,並且您有一個阻擋 .*google.com 的規則,則連線將被阻擋。&lt;br/&gt;&lt;/p&gt;&lt;p&gt;如果您選取此框,則必須指定您要篩選的確切字串(網域、執行檔、命令列)。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1089"/>
+        <source>Case-sensitive</source>
+        <translation>區分大小寫</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1096"/>
+        <source>Don&apos;t log connections that match this rule</source>
+        <translation>不記錄符合此規則的連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1099"/>
+        <source>Don&apos;t log connections</source>
+        <translation>不記錄連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/ruleseditor.ui" line="1145"/>
+        <source>Description...</source>
+        <translation>描述...</translation>
+    </message>
+</context>
+<context>
+    <name>StatsDialog</name>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="34"/>
+        <source>OpenSnitch Network Statistics</source>
+        <translation>OpenSnitch 網路統計</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="94"/>
+        <source>Filter</source>
+        <translation>篩選</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1814"/>
+        <source>-</source>
+        <translation>-</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="107"/>
+        <source>Allow</source>
+        <translation>允許</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="116"/>
+        <source>Deny</source>
+        <translation>阻擋</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="125"/>
+        <source>Reject</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="143"/>
+        <source>Ex.: firefox</source>
+        <translation>例如:firefox</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="180"/>
+        <source>0</source>
+        <translation>0</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="205"/>
+        <source>50</source>
+        <translation>50</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="210"/>
+        <source>100</source>
+        <translation>100</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="215"/>
+        <source>200</source>
+        <translation>200</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="220"/>
+        <source>300</source>
+        <translation>300</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="233"/>
+        <source>Delete all intercepted events</source>
+        <translation>刪除所有被攔截的事件</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="333"/>
+        <source>Create a new rule</source>
+        <translation>建立新規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="376"/>
+        <source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
+        <translation>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;主機名稱 - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="413"/>
+        <source>Status</source>
+        <translation>狀態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="451"/>
+        <source>Start or Stop interception</source>
+        <translation>開始或停止攔截</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="496"/>
+        <source>Events</source>
+        <translation>事件</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="829"/>
+        <source>Nodes</source>
+        <translation>節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="640"/>
+        <source>Delete this node</source>
+        <translation>刪除此節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="656"/>
+        <source>Show the preferences of this node</source>
+        <translation>顯示此節點的偏好設定</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="672"/>
+        <source>Start or stop interception of this node</source>
+        <translation>開始或停止攔截此節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1721"/>
+        <source>Rules</source>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="780"/>
+        <source>2</source>
+        <translation>2</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="785"/>
+        <source>Application rules</source>
+        <translation>應用程式規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="939"/>
+        <source>Permanent</source>
+        <translation>永久</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="948"/>
+        <source>Temporary</source>
+        <translation>臨時</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="957"/>
+        <source>System rules</source>
+        <translation>系統規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="930"/>
+        <source>All applications</source>
+        <translation>所有應用程式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="998"/>
+        <source>enable</source>
+        <translation>啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1028"/>
+        <source>Edit rule</source>
+        <translation>編輯規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1045"/>
+        <source>Delete rule</source>
+        <translation>刪除規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1069"/>
+        <source>Hosts</source>
+        <translation>主機</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1162"/>
+        <source>Applications</source>
+        <translation>應用程式</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1278"/>
+        <source>Addresses</source>
+        <translation>位址</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1371"/>
+        <source>Ports</source>
+        <translation>連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1458"/>
+        <source>Users</source>
+        <translation>使用者</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1565"/>
+        <source>Connections</source>
+        <translation>連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1617"/>
+        <source>Dropped</source>
+        <translation>已丟棄</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1669"/>
+        <source>Uptime</source>
+        <translation>運作時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/res/stats.ui" line="1788"/>
+        <source>Version</source>
+        <translation>版本</translation>
+    </message>
+</context>
+<context>
+    <name>contextual_menu</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="48"/>
+        <source>Statistics</source>
+        <translation>統計</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="49"/>
+        <source>Enable</source>
+        <translation>啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="50"/>
+        <source>Disable</source>
+        <translation>停用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="51"/>
+        <source>Help</source>
+        <translation>幫助</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="52"/>
+        <source>Close</source>
+        <translation>關閉</translation>
+    </message>
+</context>
+<context>
+    <name>firewall</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="96"/>
+        <source>Configuration applied.</source>
+        <translation>設定已套用。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="99"/>
+        <source>Error: {0}</source>
+        <translation>錯誤: {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="198"/>
+        <source>Applying changes...</source>
+        <translation>正在套用更改...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="235"/>
+        <source>Error getting INPUT chain policy</source>
+        <translation>取得 INPUT 鏈策略時出錯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="242"/>
+        <source>Error getting OUTPUT chain policy</source>
+        <translation>取得 OUTPUT 鏈策略時出錯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="295"/>
+        <source>In order to configure firewall rules from the GUI, we need to use &apos;nftables&apos; instead of &apos;iptables&apos;</source>
+        <translation>為了從 GUI 設定防火牆規則,我們需要使用 &apos;nftables&apos; 而不是 &apos;iptables&apos;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="309"/>
+        <source>Enabling firewall...</source>
+        <translation>正在啟用防火牆...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall.py" line="311"/>
+        <source>Disabling firewall...</source>
+        <translation>正在停用防火牆...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="71"/>
+        <source>Dest Port</source>
+        <translation>目標連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="72"/>
+        <source>Source Port</source>
+        <translation>來源連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="73"/>
+        <source>Dest IP</source>
+        <translation>目標 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="74"/>
+        <source>Source IP</source>
+        <translation>來源 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="75"/>
+        <source>Input interface</source>
+        <translation>輸入介面</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="76"/>
+        <source>Output interface</source>
+        <translation>輸出介面</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="77"/>
+        <source>Set conntrack mark</source>
+        <translation>設定 conntrack 標記</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="78"/>
+        <source>Match conntrack mark</source>
+        <translation>符合 conntrack 標記</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="79"/>
+        <source>Match conntrack state(s)</source>
+        <translation>符合 conntrack 狀態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="80"/>
+        <source>Set mark on packet</source>
+        <translation>在封包上設定標記</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="81"/>
+        <source>Match packet information</source>
+        <translation>符合封包資訊</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="87"/>
+        <source>Bandwidth quotas</source>
+        <translation>頻寬配額</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="89"/>
+        <source>Rate limit connections</source>
+        <translation>限制連接速率</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="108"/>
+        <source>
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+</source>
+        <translation>
+支援的格式:
+
+ - 簡易:23
+ - 範圍:80-1024
+ - 多個連接埠:80,443,8080
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="134"/>
+        <source>
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+</source>
+        <translation>
+支援的格式:
+
+ - 簡易:1.2.3.4
+ - IP 範圍:1.2.3.100-1.2.3.200
+ - 網路範圍:1.2.3.4/24
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="147"/>
+        <source>Match input interface. Regular expressions not allowed.
+Use * to match multiple interfaces.</source>
+        <translation>符合輸入介面。不允許使用正規表達式。
+使用 * 符號符合多個介面。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="155"/>
+        <source>Match output interface. Regular expressions not allowed.
+Use * to match multiple interfaces.</source>
+        <translation>符合輸出介面。不允許使用正規表達式。
+使用 * 符號符合多個介面。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="163"/>
+        <source>Set a conntrack mark on the connection, in decimal format.</source>
+        <translation>在連線上設定 conntrack 標記,以十進位格式。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="173"/>
+        <source>Match a conntrack mark of the connection, in decimal format.</source>
+        <translation>符合連線的 conntrack 標記,以十進位格式。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="180"/>
+        <source>Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+</source>
+        <translation>符合 conntrack 狀態。
+
+支援的格式:
+ - 簡易:new
+ - 逗號分隔狀態:related,new
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="195"/>
+        <source>
+Match packet&apos;s metainformation.
+
+Value must be in decimal format, except for the &quot;l4proto&quot; option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it&apos;ll use it as the code of
+that protocol.
+</source>
+        <translation>
+符合封包的附加資訊。
+
+值必須使用十進位格式,唯獨 &quot;l4proto&quot; 選項除外。
+對於 l4proto,它可以是小寫字串,例如:
+ tcp
+ udp
+ icmp
+ 等等
+
+如果協議或 lproto 的值為十進位,它將用作該協議的代碼。
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="215"/>
+        <source>Set a mark on the packet matching the specified conditions. The value is in decimal format.</source>
+        <translation>在符合指定條件的封包上設定標記。值為十進位格式。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="223"/>
+        <source>
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation>
+符合 ICMP 代碼。
+
+支援的格式:
+ - 簡易:echo-request
+ - 逗號分隔:echo-request,echo-reply
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="236"/>
+        <source>
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+</source>
+        <translation>
+符合 ICMPv6 代碼。
+
+支援的格式:
+ - 簡易:echo-request
+ - 逗號分隔:echo-request,echo-reply
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="249"/>
+        <source>Print a message when this rule matches a packet.</source>
+        <translation>當此規則符合封包時,列印一條訊息。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="256"/>
+        <source>
+Apply quotas on connections.
+
+For example when:
+ - &quot;quota over 10/mbytes&quot; -&gt; apply the Action defined (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+</source>
+        <translation>
+對連線套用配額。
+
+例如:
+ - &quot;quota over 10/mbytes&quot; -&gt; 套用定義的動作 (DROP)
+ - &quot;quota until 10/mbytes&quot; -&gt; 套用定義的動作 (ACCEPT)
+
+值必須為 VALUE/UNITS 格式,例如:
+ - 10mbytes, 1/gbytes, 等等
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="288"/>
+        <source>
+Apply limits on connections.
+
+For example when:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; apply the Action defined (DROP, ACCEPT, etc)
+    (When there&apos;re more than 10MB per minute, apply an Action)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+</source>
+        <translation>
+對連線套用限制。
+
+例如:
+ - &quot;limit over 10/mbytes/minute&quot; -&gt; 套用定義的動作 (DROP, ACCEPT, 等等)
+    (當每分鐘超過 10MB 時,套用一個動作)
+
+ - &quot;limit until 10/mbytes/hour&quot; -&gt; 套用定義的動作 (ACCEPT)
+
+值必須為 VALUE/UNITS/TIME 格式,例如:
+ - 10/mbytes/minute, 1/gbytes/hour, 等等
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="376"/>
+        <source>Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior
+(pip3 install --ignore-installed protobuf==3.8.0)</source>
+        <translation>您的 protobuf 版本不相容,您需要安裝 protobuf 3.8.0 或更高版本
+(pip3 install --ignore-installed protobuf==3.8.0)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="405"/>
+        <source>Rule deleted</source>
+        <translation>規則已刪除</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="411"/>
+        <source>Rule saved</source>
+        <translation>規則已儲存</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="413"/>
+        <source>Rule added</source>
+        <translation>規則已新增</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="423"/>
+        <source>Error saving rule</source>
+        <translation>儲存規則時出錯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="450"/>
+        <source>You can use &apos;,&apos; or &apos;-&apos; to specify multiple ports/IPs or ranges/values:&lt;br&gt;&lt;br&gt;ports: 22 or 22,443 or 50000-60000&lt;br&gt;IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130&lt;br&gt;Values: echo-reply,echo-request&lt;br&gt;Values: new,established,related</source>
+        <translation>您可以使用 &apos;,&apos; 或 &apos;-&apos; 來指定多個連接埠/IP 或範圍/值:&lt;br&gt;&lt;br&gt;連接埠:22 或 22,443 或 50000-60000&lt;br&gt;IP:192.168.1.1 或 192.168.1.30-192.168.1.130&lt;br&gt;值:echo-reply,echo-request&lt;br&gt;值:new,established,related</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="470"/>
+        <source>Deleting rule, wait</source>
+        <translation>正在刪除規則,請稍候</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="473"/>
+        <source>Error updating rule</source>
+        <translation>更新規則時出錯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="508"/>
+        <source>Add at least one statement.</source>
+        <translation>至少新增一個語句。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="519"/>
+        <source>Adding rule, wait</source>
+        <translation>正在新增規則,請稍候</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="529"/>
+        <source>&lt;select a statement&gt;</source>
+        <translation>&lt;選擇一個語句&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="654"/>
+        <source>num</source>
+        <translation>數字</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="668"/>
+        <source>to</source>
+        <translation>到</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="834"/>
+        <source>Equal</source>
+        <translation>等於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="835"/>
+        <source>Not equal</source>
+        <translation>不等於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="836"/>
+        <source>Greater or equal than</source>
+        <translation>大於或等於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="837"/>
+        <source>Greater than</source>
+        <translation>大於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="838"/>
+        <source>Less or equal than</source>
+        <translation>小於或等於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="839"/>
+        <source>Less than</source>
+        <translation>小於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1007"/>
+        <source>Warning: ct set mark value is empty, malformed rule?</source>
+        <translation>警告:ct 設定標記值為空,規則格式錯誤?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1523"/>
+        <source>Firewall rule</source>
+        <translation>防火牆規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1059"/>
+        <source>Simple</source>
+        <translation>簡易</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1064"/>
+        <source>Advanced</source>
+        <translation>進階</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1186"/>
+        <source>This rule is not supported yet.</source>
+        <translation>此規則目前尚未支援。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1251"/>
+        <source>Exclude service</source>
+        <translation>排除服務</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1263"/>
+        <source>Allow inbound connections to the selected port.</source>
+        <translation>允許到選定連接埠的對內連接。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1265"/>
+        <source>Allow outbound connections to the selected port.</source>
+        <translation>允許到選定連接埠的對外連接。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1341"/>
+        <source>select a statement.</source>
+        <translation>選擇一個語句。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1357"/>
+        <source>value cannot be 0 or empty.</source>
+        <translation>值不能為 0 或空。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1369"/>
+        <source>the value format is 1024/kbytes (or bytes, mbytes, gbytes)</source>
+        <translation>值的格式為 1024/kbytes (或 bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1383"/>
+        <source>the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)</source>
+        <translation>值的格式為 1024/kbytes/second (或 bytes, mbytes, gbytes)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1386"/>
+        <source>rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.</source>
+        <translation>速率限制無效,使用:bytes, kbytes, mbytes 或 gbytes。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1388"/>
+        <source>time-limit not valid, use: second, minute, hour or day</source>
+        <translation>時間限制無效,使用:second, minute, hour 或 day</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/firewall_rule.py" line="1455"/>
+        <source>port not valid.</source>
+        <translation>連接埠無效。</translation>
+    </message>
+</context>
+<context>
+    <name>messages</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="301"/>
+        <source>Info</source>
+        <translation>資訊</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="305"/>
+        <source>Error</source>
+        <translation>錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="309"/>
+        <source>Warning</source>
+        <translation>警告</translation>
+    </message>
+</context>
+<context>
+    <name>notifications</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="767"/>
+        <source>System notifications are not available, you need to install python3-notify2.</source>
+        <translation>系統通知無法使用,您需要安裝 python3-notify2 套件。</translation>
+    </message>
+</context>
+<context>
+    <name>popups</name>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="42"/>
+        <source>Open</source>
+        <translation>開啟</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="119"/>
+        <source>Allow</source>
+        <translation>允許</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="118"/>
+        <source>Deny</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/notifications.py" line="114"/>
+        <source>New outgoing connection</source>
+        <translation>新的對外連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="494"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt; on %s port %d</source>
+        <translation>正在連線到 &lt;b&gt;%s&lt;/b&gt; 的 %s 連接埠 %d</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="52"/>
+        <source>until reboot</source>
+        <translation>直到重新啟動</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="54"/>
+        <source>forever</source>
+        <translation>永久</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="120"/>
+        <source>Reject</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="335"/>
+        <source>Outgoing connection</source>
+        <translation>對外連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="340"/>
+        <source>Process launched from:</source>
+        <translation>處理程序起始來源:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="377"/>
+        <source>from this executable</source>
+        <translation>來自此執行檔</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="381"/>
+        <source>from this command line</source>
+        <translation>來自此命令列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="383"/>
+        <source>to port {0}</source>
+        <translation>到埠號 {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="446"/>
+        <source>to {0}</source>
+        <translation>到 {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="386"/>
+        <source>from user {0}</source>
+        <translation>來自使用者 {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="390"/>
+        <source>from this PID</source>
+        <translation>來自此 PID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="403"/>
+        <source>to {0}.*</source>
+        <translation>到 {0}.*</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="456"/>
+        <source>to *.{0}</source>
+        <translation>到 *.{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="490"/>
+        <source>&lt;b&gt;Remote&lt;/b&gt; process %s running on &lt;b&gt;%s&lt;/b&gt;</source>
+        <translation>&lt;b&gt;遠端&lt;/b&gt; 處理程序 %s 在 &lt;b&gt;%s&lt;/b&gt; 上執行</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="501"/>
+        <source>is connecting to &lt;b&gt;%s&lt;/b&gt;, %s</source>
+        <translation>正在連線到 &lt;b&gt;%s&lt;/b&gt;,%s</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/prompt.py" line="506"/>
+        <source>is attempting to resolve &lt;b&gt;%s&lt;/b&gt; via %s, %s port %d</source>
+        <translation>正試圖透過 %s,%s 連接埠 %d 解析 &lt;b&gt;%s&lt;/b&gt;</translation>
+    </message>
+</context>
+<context>
+    <name>preferences</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="458"/>
+        <source>Warning</source>
+        <translation>警告</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="44"/>
+        <source>Restart the GUI in order changes to take effect</source>
+        <translation>重新啟動 GUI 以使變更生效</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="438"/>
+        <source>There&apos;re no nodes connected</source>
+        <translation>沒有任何節點已連線。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="183"/>
+        <source>System default</source>
+        <translation>系統預設</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="534"/>
+        <source>System</source>
+        <translation>系統</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="206"/>
+        <source>Themes not available. Install qt-material: pip3 install qt-material</source>
+        <translation>主題不可用。安裝 qt-material:pip3 install qt-material</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="337"/>
+        <source>Server address can not be empty</source>
+        <translation>伺服器位址不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="368"/>
+        <source>Error loading {0} configuration</source>
+        <translation>載入 {0} 設定時出錯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="436"/>
+        <source>Exception saving config: {0}</source>
+        <translation>儲存設定時發生例外:{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="452"/>
+        <source>DB type changed</source>
+        <translation>DB 類型已變更</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="458"/>
+        <source>You must select a file for the database&lt;br&gt;or choose &quot;In memory&quot; type.</source>
+        <translation>您必須為資料庫選擇一個檔案&lt;br&gt;或是選擇「記憶體中」的類型。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="490"/>
+        <source>Certificates changed</source>
+        <translation>憑證已變更</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="504"/>
+        <source>Language changed</source>
+        <translation>語言已變更</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="535"/>
+        <source>UI theme changed</source>
+        <translation>UI 主題已變更</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="565"/>
+        <source>Applying configuration on {0} ...</source>
+        <translation>正在對 {0} 套用設定 ...</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="573"/>
+        <source>Ok</source>
+        <translation>確定</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="583"/>
+        <source>Exception saving node config {0}: {1}</source>
+        <translation>儲存節點設定 {0} 時發生例外:{1}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="594"/>
+        <source>Certs fields cannot be empty.</source>
+        <translation>憑證欄位不能為空。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="597"/>
+        <source>cert file has excessive permissions, it should have 0600</source>
+        <translation>憑證檔案權限過大,應設為 0600</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="601"/>
+        <source>cert key file has excessive permissions, it should have 0600</source>
+        <translation>憑證金鑰檔案權限過大,應設為 0600</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="607"/>
+        <source>CA cert file has excessive permissions, it should have 0600</source>
+        <translation>CA 憑證檔案權限過大,應設為 0600</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="667"/>
+        <source>Configuration applied.</source>
+        <translation>設定已套用。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="669"/>
+        <source>Error applying configuration: {0}</source>
+        <translation>套用設定時出錯:{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="674"/>
+        <source>Certs changed</source>
+        <translation>憑證已變更</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="710"/>
+        <source>Hover the mouse over the texts to display the help&lt;br&gt;&lt;br&gt;Don&apos;t forget to visit the wiki: &lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</source>
+        <translation>將滑鼠停在文字上以顯示幫助&lt;br&gt;&lt;br&gt;別忘了造訪 wiki:&lt;a href=&quot;{0}&quot;&gt;{0}&lt;/a&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/preferences.py" line="737"/>
+        <source>Auth type changed</source>
+        <translation>認證類型已變更</translation>
+    </message>
+</context>
+<context>
+    <name>proc_details</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="100"/>
+        <source>&lt;b&gt;Error loading process information:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</source>
+        <translation>&lt;b&gt;載入處理程序資訊時出錯:&lt;/b&gt; &lt;br&gt;&lt;br&gt;
+
+</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="119"/>
+        <source>&lt;b&gt;Error stopping monitoring process:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <translation>&lt;b&gt;停止監控處理程序時出錯:&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/processdetails.py" line="159"/>
+        <source>loading...</source>
+        <translation>載入中...</translation>
+    </message>
+</context>
+<context>
+    <name>rules</name>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="238"/>
+        <source>There&apos;re no nodes connected.</source>
+        <translation>沒有已連線的節點。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="255"/>
+        <source>There&apos;s already a rule with this name.</source>
+        <translation>已經有一條相同名稱的規則。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="281"/>
+        <source>Rule applied.</source>
+        <translation>規則已套用。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="283"/>
+        <source>Error applying rule: {0}</source>
+        <translation>套用規則出錯:{0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="549"/>
+        <source>&lt;b&gt;Error loading rule&lt;/b&gt;</source>
+        <translation>&lt;b&gt;載入規則出錯&lt;/b&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="651"/>
+        <source>protocol can not be empty, or uncheck it</source>
+        <translation>通訊協定不能為空或取消勾選</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="665"/>
+        <source>Protocol regexp error</source>
+        <translation>通訊協定正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="669"/>
+        <source>process path can not be empty</source>
+        <translation>處理程序路徑不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="683"/>
+        <source>Process path regexp error</source>
+        <translation>處理程序路徑正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="687"/>
+        <source>command line can not be empty</source>
+        <translation>命令列不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="701"/>
+        <source>Command line regexp error</source>
+        <translation>命令列正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="705"/>
+        <source>Network interface can not be empty</source>
+        <translation>網路介面不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="719"/>
+        <source>Network interface regexp error</source>
+        <translation>網路介面正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="723"/>
+        <source>Source port can not be empty</source>
+        <translation>來源連接埠不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="737"/>
+        <source>Source port regexp error</source>
+        <translation>來源連接埠正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="741"/>
+        <source>Dest port can not be empty</source>
+        <translation>目標連接埠不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="755"/>
+        <source>Dst port regexp error</source>
+        <translation>目標連接埠正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="759"/>
+        <source>Dest host can not be empty</source>
+        <translation>目標主機不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="773"/>
+        <source>Dst host regexp error</source>
+        <translation>目標主機正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="777"/>
+        <source>Source IP/Network can not be empty</source>
+        <translation>來源 IP/網路不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="803"/>
+        <source>Source IP regexp error</source>
+        <translation>來源 IP 正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="815"/>
+        <source>Dest IP/Network can not be empty</source>
+        <translation>目標 IP/網路不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="841"/>
+        <source>Dst IP regexp error</source>
+        <translation>目標 IP 正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="856"/>
+        <source>User ID can not be empty</source>
+        <translation>使用者 ID 不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="873"/>
+        <source>User ID regexp error</source>
+        <translation>使用者 ID 正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="876"/>
+        <source>Invalid UID, it must be a digit.</source>
+        <translation>無效的 UID,必須是數字。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="890"/>
+        <source>PID field can not be empty</source>
+        <translation>PID 欄位不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="904"/>
+        <source>PID field regexp error</source>
+        <translation>PID 欄位正規表達式錯誤</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="960"/>
+        <source>Lists field cannot be empty</source>
+        <translation>列表欄位不能為空</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="962"/>
+        <source>Lists field must be a directory</source>
+        <translation>列表欄位必須是目錄</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="992"/>
+        <source>Select at least one field.</source>
+        <translation>至少選擇一個欄位。</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/ruleseditor.py" line="1005"/>
+        <source>&lt;b&gt;Rule not supported&lt;/b&gt;</source>
+        <translation>&lt;b&gt;不支援的規則&lt;/b&gt;</translation>
+    </message>
+</context>
+<context>
+    <name>stats</name>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="231"/>
+        <source>WARNING</source>
+        <translation>警告</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/service.py" line="796"/>
+        <source>New node connected</source>
+        <translation>新節點已連接</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="17"/>
+        <source>What</source>
+        <translation>什麼</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="18"/>
+        <source>Hits</source>
+        <translation>命中次數</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/customwidgets/addresstablemodel.py" line="19"/>
+        <source>Network name</source>
+        <translation>網路名稱</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="293"/>
+        <source>Time</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="297"/>
+        <source>Node</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>節點</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="294"/>
+        <source>Action</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="304"/>
+        <source>Destination</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>目的地</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="301"/>
+        <source>Protocol</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>協議</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="302"/>
+        <source>Process</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>處理程序</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="308"/>
+        <source>Rule</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="286"/>
+        <source>Name</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>名稱</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="287"/>
+        <source>Address</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>位址</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="288"/>
+        <source>Status</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>狀態</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="289"/>
+        <source>Hostname</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>主機名稱</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="290"/>
+        <source>Uptime</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>運作時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="422"/>
+        <source>Version</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>版本</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="419"/>
+        <source>Rules</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="295"/>
+        <source>Duration</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>持續時間</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="296"/>
+        <source>Description</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>描述</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="298"/>
+        <source>Enabled</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>已啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="299"/>
+        <source>Precedence</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>優先順序</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="437"/>
+        <source>Hits</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>命中次數</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="303"/>
+        <source>Cmdline</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>命令列</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="305"/>
+        <source>DstIP</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>目標 IP</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="306"/>
+        <source>DstHost</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>目標主機</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="307"/>
+        <source>DstPort</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>目標連接埠</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="309"/>
+        <source>UserID</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>使用者 ID</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="310"/>
+        <source>LastConnection</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>最後連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="312"/>
+        <source>Not running</source>
+        <translation>未運作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="313"/>
+        <source>Disabled</source>
+        <translation>已停用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="314"/>
+        <source>Running</source>
+        <translation>運作中</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="405"/>
+        <source>Export rules</source>
+        <translation>匯出規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="406"/>
+        <source>Import rules</source>
+        <translation>匯入規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="407"/>
+        <source>Export events to CSV</source>
+        <translation>將事件匯出至 CSV</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="408"/>
+        <source>Quit</source>
+        <translation>退出</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="420"/>
+        <source>Connections</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>連線</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="421"/>
+        <source>Dropped</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>已丟棄</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="436"/>
+        <source>What</source>
+        <comment>This is a word, without spaces and symbols.</comment>
+        <translation>內容</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="635"/>
+        <source>OpenSnitch Network Statistics {0}</source>
+        <translation>OpenSnitch 網路統計 {0}</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="637"/>
+        <source>OpenSnitch Network Statistics for {0}</source>
+        <translation>OpenSnitch 為 {0} 的網路統計</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="832"/>
+        <source>Details</source>
+        <translation>詳細資訊</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="833"/>
+        <source>Rules</source>
+        <translation>規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="834"/>
+        <source>New</source>
+        <translation>新增</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="942"/>
+        <source>Export</source>
+        <translation>匯出</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="875"/>
+        <source>Action</source>
+        <translation>動作</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="964"/>
+        <source>Disable</source>
+        <translation>停用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="966"/>
+        <source>Enable</source>
+        <translation>啟用</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="971"/>
+        <source>Delete</source>
+        <translation>刪除</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="970"/>
+        <source>Edit</source>
+        <translation>編輯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="974"/>
+        <source>To clipboard</source>
+        <translation>複製到剪貼簿</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="941"/>
+        <source>Apply to</source>
+        <translation>套用於</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="950"/>
+        <source>Allow</source>
+        <translation>允許</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="951"/>
+        <source>Deny</source>
+        <translation>阻擋</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="952"/>
+        <source>Reject</source>
+        <translation>拒絕</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="955"/>
+        <source>Always</source>
+        <translation>總是</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="956"/>
+        <source>Until reboot</source>
+        <translation>持續到重新啟動</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="969"/>
+        <source>Duplicate</source>
+        <translation>複製</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="975"/>
+        <source>To disk</source>
+        <translation>儲存到磁碟</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1730"/>
+        <source>    Are you sure?</source>
+        <translation>    您確定嗎?</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2559"/>
+        <source>Select a directory to export rules</source>
+        <translation>選擇一個目錄以匯出規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1207"/>
+        <source>    Your are about to delete this rule.    </source>
+        <translation>    您即將刪除此規則。    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1209"/>
+        <source>    Your are about to delete this entry.    </source>
+        <translation>    您即將刪除此條目。    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1266"/>
+        <source>Rule not found by that name and node</source>
+        <translation>未找到該名稱和節點的規則</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1319"/>
+        <source>Error:</source>
+        <translation>錯誤:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1327"/>
+        <source>Warning:</source>
+        <translation>警告:</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1697"/>
+        <source>    You are about to delete this node.    </source>
+        <translation>    您即將刪除此節點。    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1706"/>
+        <source>&lt;b&gt;Error deleting node&lt;/b&gt;&lt;br&gt;&lt;br&gt;</source>
+        <comment>{0}</comment>
+        <translation>&lt;b&gt;刪除節點時出錯&lt;/b&gt;&lt;br&gt;&lt;br&gt;</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="1730"/>
+        <source>    You are about to delete this rule.    </source>
+        <translation>    您即將刪除此規則。    </translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2514"/>
+        <source>Error exporting rules</source>
+        <translation>匯出規則時出錯</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2588"/>
+        <source>Select a directory with rules to import (JSON files)</source>
+        <translation>選擇一個含有要匯入的規則的目錄(JSON 檔案)</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2602"/>
+        <source>Rules imported fine</source>
+        <translation>規則匯入成功</translation>
+    </message>
+    <message>
+        <location filename="../../../opensnitch/dialogs/stats.py" line="2617"/>
+        <source>Save as CSV</source>
+        <translation>另存為 CSV</translation>
+    </message>
+</context>
+</TS>
diff --git a/ui/i18n/opensnitch_i18n.pro b/ui/i18n/opensnitch_i18n.pro
new file mode 100644 (file)
index 0000000..6732729
--- /dev/null
@@ -0,0 +1,40 @@
+#TEMPLATE = app
+#TARGET = ts
+#INCLUDEPATH += opensnitch
+
+
+# Input
+SOURCES +=  ../opensnitch/service.py \
+           ../opensnitch/notifications.py \
+           ../opensnitch/customwidgets/addresstablemodel.py \
+           ../opensnitch/customwidgets/main.py \
+           ../opensnitch/dialogs/prompt.py \
+           ../opensnitch/dialogs/preferences.py \
+           ../opensnitch/dialogs/ruleseditor.py \
+           ../opensnitch/dialogs/processdetails.py \
+           ../opensnitch/dialogs/stats.py \
+           ../opensnitch/dialogs/firewall.py \
+           ../opensnitch/dialogs/firewall_rule.py
+
+FORMS += ../opensnitch/res/prompt.ui \
+           ../opensnitch/res/ruleseditor.ui \
+           ../opensnitch/res/preferences.ui \
+           ../opensnitch/res/process_details.ui \
+           ../opensnitch/res/stats.ui \
+           ../opensnitch/res/firewall.ui \
+           ../opensnitch/res/firewall_rule.ui
+TRANSLATIONS += locales/de_DE/opensnitch-de_DE.ts \
+                locales/es_ES/opensnitch-es_ES.ts \
+                locales/eu_ES/opensnitch-eu_ES.ts \
+                locales/hu_HU/opensnitch-hu_HU.ts \
+                locales/ja_JP/opensnitch-ja_JP.ts \
+                locales/pt_BR/opensnitch-pt_BR.ts \
+                locales/ro_RO/opensnitch-ro_RO.ts \
+                locales/fr_FR/opensnitch-fr_FR.ts \
+                locales/lt_LT/opensnitch-lt_LT.ts \
+                locales/tr_TR/opensnitch-tr_TR.ts \
+                locales/ru_RU/opensnitch-ru_RU.ts \
+                locales/nb_NO/opensnitch-nb_NO.ts \
+                locales/nl_NL/opensnitch-nl_NL.ts \
+                locales/fi_FI/opensnitch-fi_FI.ts \
+                locales/zh_TW/opensnitch-zh_TW.ts
diff --git a/ui/opensnitch/__init__.py b/ui/opensnitch/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ui/opensnitch/actions/__init__.py b/ui/opensnitch/actions/__init__.py
new file mode 100644 (file)
index 0000000..3b145e7
--- /dev/null
@@ -0,0 +1,159 @@
+from PyQt5.QtCore import QObject
+
+import json
+import os
+import glob
+import sys
+
+from opensnitch.utils.xdg import xdg_config_home
+from opensnitch.actions import highlight
+from opensnitch.actions.default_configs import commonDelegateConfig, rulesDelegateConfig, fwDelegateConfig
+
+class Actions(QObject):
+    """List of actions to perform on the data that is displayed on the GUI.
+    Whenever an item matches a condition an action is applied, for example:
+        - if the text of a cell matches a condition for the given columns,
+        then the properties of the cell/row and the text are customized.
+
+    There's only 1 action supported right now:
+        - highlight: for customizing rows and cells appearance.
+
+    There're 3 actions by default of type Highlight:
+        - rules: applied to the rules to colorize the columns Enabled and
+        Action
+        - firewall: applied to the fw rules to colorize the columns Action and
+        Target.
+        - common: applied to the rest of the views to colorize the column
+        Action.
+
+    Users can modify the default actions, by adding more patterns to colorize.
+    At the same time they can also create new actions to be applied on certain views.
+
+    The format of the actions is JSON:
+        {
+        "created": "....",
+        "name": "...",
+        "actions": {
+            "highlight": {
+                "cells": [
+                        {
+                            "text": ["allow", "True", "online"],
+                            "cols": [3,5,6],
+                            "color": "green",
+                        },
+                        {
+                            "text": ["deny", "False", "offline"],
+                            "cols": [3,5,6],
+                            "color": "red",
+                        }
+                    ],
+                "rows": []
+            }
+        }
+
+    """
+    __instance = None
+
+    # list of loaded actions
+    _actions = None
+
+
+    KEY_ACTIONS = "actions"
+    KEY_NAME = "name"
+    KEY_TYPE = "type"
+
+    # TODO: emit a signal when the actions are (re)loaded
+    # reloaded_signal = pyQtSignal()
+
+    # default paths to look for actions
+    _paths = [
+        os.path.dirname(sys.modules[__name__].__file__) + "/data/",
+        "{0}/{1}".format(xdg_config_home, "/opensnitch/actions/")
+    ]
+
+    @staticmethod
+    def instance():
+        if Actions.__instance == None:
+            Actions.__instance = Actions()
+        return Actions.__instance
+
+    def __init__(self, parent=None):
+        QObject.__init__(self)
+        self._actions_list = {}
+        try:
+            base_dir = "{0}/{1}".format(xdg_config_home, "/opensnitch/actions/")
+            os.makedirs(base_dir, 0o700)
+        except:
+            pass
+
+    def _load_default_configs(self):
+        self._actions_list[commonDelegateConfig[Actions.KEY_NAME]] = self.compile(commonDelegateConfig)
+        self._actions_list[rulesDelegateConfig[Actions.KEY_NAME]] = self.compile(rulesDelegateConfig)
+        self._actions_list[fwDelegateConfig[Actions.KEY_NAME]] = self.compile(fwDelegateConfig)
+
+    def loadAll(self):
+        """look for actions firstly on default system path, secondly on
+        user's home.
+        If a user customizes existing configurations, they'll be saved under
+        the user's home directory.
+
+        Action files are .json files.
+        """
+        self._load_default_configs()
+
+        for path in self._paths:
+            for jfile in glob.glob(os.path.join(path, '*.json')):
+                self.load(jfile)
+
+    def load(self, action_file):
+        """read a json file from disk and create the action."""
+        with open(action_file, 'r') as fd:
+            data=fd.read()
+            obj = json.loads(data)
+            self._actions_list[obj[Actions.KEY_NAME]] = self.compile(obj)
+
+    def compile(self, obj):
+        try:
+            if Actions.KEY_NAME not in obj or obj[Actions.KEY_NAME] == "":
+                return None
+            if obj.get(Actions.KEY_ACTIONS) == None:
+                return None
+
+            for action in obj[Actions.KEY_ACTIONS]:
+                if action == highlight.Highlight.NAME:
+                    h = highlight.Highlight(obj[Actions.KEY_ACTIONS][action])
+                    h.compile()
+                    obj[Actions.KEY_ACTIONS][action]= h
+                else:
+                    print("Actions exception: Action '{0}' not supported yet".format(obj[Actions.KEY_NAME]))
+
+            return obj
+        except Exception as e:
+            print("Actions.compile() exception:", e)
+            return None
+
+
+
+    def getAll(self):
+        return self._actions_list
+
+    def deleteAll(self):
+        self._actions_list = {}
+
+    def get(self, name):
+        try:
+            return self._actions_list[name]
+        except Exception as e:
+            print("get() exception:", e)
+            return None
+
+    def delete(self, name):
+        try:
+            del self._actions_list[name]
+            # TODO:
+            # self.reloaded_signal.emit()
+        except:
+            pass
+
+    def isValid(self):
+        pass
diff --git a/ui/opensnitch/actions/default_configs.py b/ui/opensnitch/actions/default_configs.py
new file mode 100644 (file)
index 0000000..fae8b8b
--- /dev/null
@@ -0,0 +1,128 @@
+
+# common configuration to highlight Action column
+commonDelegateConfig = {
+  "name": "commonDelegateConfig",
+  "created": "",
+  "updated": "",
+  "actions": {
+    "highlight": {
+      "cells": [
+        {
+          "text": ["allow", "\u2713 online"],
+          "operator": "==",
+          "cols": [1, 2, 3],
+          "color": "green",
+          "bgcolor": "",
+          "alignment": ["center"]
+        },
+        {
+          "text": ["deny", "\u2613 offline"],
+          "cols": [1, 2, 3],
+          "color": "red",
+          "bgcolor": "",
+          "alignment": ["center"]
+        },
+        {
+          "text": ["reject"],
+          "cols": [1, 2, 3],
+          "color": "purple",
+          "bgcolor": "",
+          "alignment": ["center"]
+        }
+      ],
+      "rows": []
+    }
+  }
+}
+
+# firewall rules configuration to highlight Enabled and Action columns
+fwDelegateConfig = {
+  "name": "defaultFWDelegateConfig",
+  "created": "",
+  "updated": "",
+  "actions": {
+    "highlight": {
+      "cells": [
+        {
+          "text": [
+            "allow",
+            "True",
+            "accept",
+            "jump",
+            "masquerade",
+            "snat",
+            "dnat",
+            "tproxy",
+            "queue",
+            "redirect",
+            "True",
+            "ACCEPT"
+          ],
+          "cols": [7, 10],
+          "color": "green",
+          "bgcolor": "",
+          "alignment": ["center"]
+        },
+        {
+          "text": [
+            "deny",
+            "False",
+            "drop",
+            "DROP",
+            "stop"
+          ],
+          "cols": [7, 10],
+          "color": "red",
+          "bgcolor": "",
+          "alignment": ["center"]
+        },
+        {
+          "text": [
+            "reject",
+            "return"
+          ],
+          "cols": [7, 10],
+          "color": "purple",
+          "bgcolor": "",
+          "alignment": ["center"]
+        }
+      ],
+      "rows": []
+    }
+  }
+}
+
+# rules configuration to highlight Enabled and Action columns
+rulesDelegateConfig = {
+  "name": "defaultRulesDelegateConfig",
+  "created": "",
+  "updated": "",
+  "actions": {
+    "highlight": {
+      "cells": [
+        {
+          "text": ["allow", "True"],
+          "cols": [3, 4],
+          "color": "green",
+          "bgcolor": "",
+          "alignment": ["center"]
+        },
+        {
+          "text": ["deny", "False"],
+          "cols": [3, 4],
+          "color": "red",
+          "bgcolor": "",
+          "alignment": ["center"]
+        },
+        {
+          "text": ["reject"],
+          "cols": [3, 4],
+          "color": "purple",
+          "bgcolor": "",
+          "alignment": ["center"]
+        }
+      ],
+      "rows": []
+    }
+  }
+}
diff --git a/ui/opensnitch/actions/highlight.py b/ui/opensnitch/actions/highlight.py
new file mode 100644 (file)
index 0000000..c76a810
--- /dev/null
@@ -0,0 +1,236 @@
+from PyQt5 import Qt, QtCore
+from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem
+
+# PyQt5 >= v5.15.8 (#821)
+if hasattr(Qt, 'QStyle'):
+    from PyQt5.Qt import QStyle
+else:
+    from PyQt5.QtWidgets import QStyle
+
+class Highlight():
+    """Customizes QTablewView cells via QItemDelegates.
+    Format:
+    [
+        {
+            'text': {"allow", "True", "online"},
+            'cols': {1,4,5},
+            'color': "green",
+            'bgcolor': None,
+            'alignment': ["center"],
+            #"margins': [0, 0]
+            #'font': {}
+        },
+    ]
+
+    text: will match any of the given texts.
+    cols: look for patterns on these columns.
+    color: colorizes the color of the text.
+    bgcolor: colorizes the background color of the cell.
+    etc.
+    """
+
+    NAME = "highlight"
+
+    MARGINS = "margins"
+    ALIGNMENT = "alignment"
+    # QtCore.Qt.AlignCenter
+    ALIGN_CENTER = "center"
+    # QtCore.Qt.AlignHCenter
+    ALIGN_HCENTER = "hcenter"
+    # QtCore.Qt.AlignVCenter
+    ALIGN_VCENTER = "vcenter"
+
+    COLOR = "color"
+    BGCOLOR = "bgcolor"
+    FONT = "font"
+    CELLS = "cells"
+    ROWS = "rows"
+    COLS = "cols"
+    TEXT = "text"
+
+    def __init__(self, config):
+        # original json config received
+        self._config = config
+        self._last_visited_row = -1
+        self._rowcells = ""
+
+    def compile(self):
+        """transform json items to Qt objects.
+        These items are transformed:
+            - color (to QColor), bgcolor (to QColor), alignment (to Qt.Align*),
+            font (to QFont TODO)
+
+            Return the original json object transformed.
+        """
+        # cells, rows
+        for idx in self._config:
+            cells = self._config[idx]
+            for cell in cells:
+                for item in cell:
+                    # colors
+                    if (item == Highlight.COLOR or item == Highlight.BGCOLOR):
+                        if cell[item] != "" and cell[item] is not None:
+                            cell[item] = QColor(cell[item])
+                        else:
+                            cell[item] = None
+
+                    # alignments
+                    if item == Highlight.ALIGNMENT:
+                        cell[item] = self.getAlignment(cell[item])
+
+                    # fonts
+                    if item == Highlight.FONT:
+                        self.getFont(cell[item])
+
+        return self._config
+
+
+    def run(self, args):
+        """Highlight cells or rows based on patterns.
+
+        Return if the cell was modified.
+
+        Keyword arguments:
+            args -- tuple of options.
+        """
+        painter = args[0]
+        option = args[1]
+        index = args[2]
+        style = args[3]
+        modelColumns = args[4]
+        curRow = args[5]
+        curColumn = args[6]
+        defaultPen = args[7]
+        defaultBrush = args[8]
+        cellAlignment = args[9]
+        cellRect = args[10]
+        cellValue = args[11]
+
+        # signal that this cell has been modified
+        modified = False
+
+        cells = self._config.get(Highlight.CELLS)
+        rows = self._config.get(Highlight.ROWS)
+
+        if cells:
+            for cell in cells:
+                if curColumn not in cell[Highlight.COLS]:
+                    continue
+                if cellValue not in cell[Highlight.TEXT]:
+                    continue
+# TODO
+#                if cell['operator'] == 'simple' and cellValue != cell['text']:
+#                    continue
+#                elif cell['text'] not in cellValue:
+#                    continue
+
+                cellColor = cell.get(Highlight.COLOR)
+                cellBgColor = cell.get(Highlight.BGCOLOR)
+                if cell.get(Highlight.ALIGNMENT) != None:
+                    cellAlignment = cell[Highlight.ALIGNMENT]
+                if cell.get(Highlight.MARGINS) != None:
+                    cellRect.adjust(
+                        int(cell[Highlight.MARGINS][self.HMARGIN]),
+                        int(cell[Highlight.MARGINS][self.VMARGIN]),
+                        -defaultPen.width(),
+                        -defaultPen.width()
+                    )
+
+                modified=True
+                self.paintCell(
+                    style,
+                    painter,
+                    option,
+                    defaultPen,
+                    cellAlignment,
+                    cellRect,
+                    cellColor,
+                    cellBgColor,
+                    cellValue)
+
+        if len(rows) == 0:
+            return (modified,)
+
+        # get row's cells only for the first cell of the row,
+        # then reuse them for the rest of the cells of the current row.
+        if curRow != self._last_visited_row:
+            self._rowcells = " ".join(
+                [index.sibling(curRow, col).data() for col in range(0, modelColumns)]
+            )
+        self._last_visited_row = curRow
+
+        for row in rows:
+            skip = True
+            for text in row[Highlight.TEXT]:
+                if text in self._rowcells:
+                    skip = False
+            if skip:
+                continue
+
+            cellColor = row.get(Highlight.COLOR)
+            cellBgColor = row.get(Highlight.BGCOLOR)
+            if row.get(Highlight.ALIGNMENT) != None:
+                cellAlignment = row[Highlight.ALIGNMENT]
+            if row.get(Highlight.MARGINS) != None:
+                cellRect.adjust(
+                    int(row[Highlight.MARGINS][self.HMARGIN]),
+                    int(row[Highlight.MARGINS][self.VMARGIN]),
+                    -defaultPen.width(),
+                    -defaultPen.width()
+                )
+
+            modified=True
+            self.paintCell(
+                style,
+                painter,
+                option,
+                defaultPen,
+                cellAlignment,
+                cellRect,
+                cellColor,
+                cellBgColor,
+                cellValue)
+
+        return (modified,)
+
+    def paintCell(self, style, painter, option, defaultPen, cellAlignment, cellRect, cellColor, cellBgColor, cellValue):
+        cellSelected = option.state & QStyle.State_Selected
+
+        painter.save()
+        # don't customize selected state
+        if not cellSelected:
+            if cellBgColor != None:
+                painter.fillRect(option.rect, cellBgColor)
+
+            if cellColor is not None:
+                defaultPen.setColor(cellColor)
+        painter.setPen(defaultPen)
+
+        # setting option.displayAlignment has no effect here, so we need to
+        # draw the text.
+        # FIXME: Drawing the text though, the background color of the SelectedState is
+        # altered.
+        # If we called super().paint(), modifying option.palette.* would be
+        # enough to change the text color, but it wouldn't be aligned:
+        # option.palette.setColor(QPalette.Text, cellColor)
+        style.drawItemText(painter, cellRect, cellAlignment, option.palette, True, cellValue)
+        painter.restore()
+
+    def getAlignment(self, alignments):
+        alignFlags = 0
+        for align in alignments:
+            if align == Highlight.ALIGN_CENTER:
+                alignFlags |= QtCore.Qt.AlignCenter
+            elif align == Highlight.ALIGN_HCENTER:
+                alignFlags |= QtCore.Qt.AlignHCenter
+            elif align == Highlight.ALIGN_VCENTER:
+                alignFlags |= QtCore.Qt.AlignVCenter
+
+        if alignFlags == 0:
+            return None
+
+        return alignFlags
+
+    def getFont(self, font):
+        # TODO
+        pass
diff --git a/ui/opensnitch/actions/utils.py b/ui/opensnitch/actions/utils.py
new file mode 100644 (file)
index 0000000..5c435be
--- /dev/null
@@ -0,0 +1,8 @@
+from PyQt5.QtGui import QColor
+
+def getColorNames():
+    """Return the built-in color names that can be used to choose new colors:
+        https://doc.qt.io/qtforpython-5/PySide2/QtGui/QColor.html#predefined-colors
+        https://www.w3.org/TR/SVG11/types.html#ColorKeywords
+    """
+    return QColor.colorNames()
diff --git a/ui/opensnitch/auth/__init__.py b/ui/opensnitch/auth/__init__.py
new file mode 100644 (file)
index 0000000..afd762b
--- /dev/null
@@ -0,0 +1,42 @@
+
+import grpc
+
+Simple = "simple"
+TLSSimple = "tls-simple"
+TLSMutual = "tls-mutual"
+
+NO_CLIENT_CERT = "no-client-cert"
+REQ_CERT = "req-cert"
+REQ_ANY_CERT = "req-any-cert"
+VERIFY_CERT = "verify-cert"
+REQ_AND_VERIFY_CERT = "req-and-verify-cert"
+
+
+def load_file(file_path):
+    try:
+        with open(file_path, "rb") as f:
+            return f.read()
+    except Exception as e:
+        print("auth: error loading {0}: {1}".format(file_path, e))
+
+    return None
+
+
+def get_tls_credentials(ca_cert, server_cert, server_key):
+    """return a new gRPC credentials object given a server cert and key file.
+    https://grpc.io/docs/guides/auth/#python
+    """
+    try:
+        cacert = load_file(ca_cert)
+        cert = load_file(server_cert)
+        cert_key = load_file(server_key)
+        auth_nodes = False if cacert == None else True
+
+        return grpc.ssl_server_credentials(
+            ((cert_key, cert),),
+            cacert,
+            auth_nodes
+        )
+    except Exception as e:
+        print("get_tls_credentials error:", e)
+        return None
diff --git a/ui/opensnitch/config.py b/ui/opensnitch/config.py
new file mode 100644 (file)
index 0000000..dd61f56
--- /dev/null
@@ -0,0 +1,255 @@
+from PyQt5 import QtCore
+from opensnitch.database import Database
+
+class Config:
+    __instance = None
+
+    HELP_URL = "https://github.com/evilsocket/opensnitch/wiki/"
+    HELP_RULES_URL = "https://github.com/evilsocket/opensnitch/wiki/Rules"
+    HELP_SYS_RULES_URL = "https://github.com/evilsocket/opensnitch/wiki/System-rules#upgrading-from-previous-versions"
+    HELP_SYSFW_URL = "https://github.com/evilsocket/opensnitch/wiki/System-rules"
+    HELP_CONFIG_URL = "https://github.com/evilsocket/opensnitch/wiki/Configurations"
+    HELP_SYSTRAY_WARN = "https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems#gui-does-not-show-up"
+
+    OPERAND_PROCESS_ID = "process.id"
+    OPERAND_PROCESS_PATH = "process.path"
+    OPERAND_PROCESS_COMMAND = "process.command"
+    OPERAND_PROCESS_ENV = "process.env."
+    OPERAND_USER_ID = "user.id"
+    OPERAND_IFACE_OUT = "iface.out"
+    OPERAND_IFACE_IN = "iface.in"
+    OPERAND_SOURCE_IP = "source.ip"
+    OPERAND_SOURCE_PORT = "source.port"
+    OPERAND_DEST_IP = "dest.ip"
+    OPERAND_DEST_HOST = "dest.host"
+    OPERAND_DEST_PORT = "dest.port"
+    OPERAND_DEST_NETWORK = "dest.network"
+    OPERAND_SOURCE_NETWORK = "source.network"
+    OPERAND_PROTOCOL = "protocol"
+    OPERAND_LIST_DOMAINS = "lists.domains"
+    OPERAND_LIST_DOMAINS_REGEXP = "lists.domains_regexp"
+    OPERAND_LIST_IPS = "lists.ips"
+    OPERAND_LIST_NETS = "lists.nets"
+
+    RULE_TYPE_LIST = "list"
+    RULE_TYPE_LISTS = "lists"
+    RULE_TYPE_SIMPLE = "simple"
+    RULE_TYPE_REGEXP = "regexp"
+    RULE_TYPE_NETWORK = "network"
+    RulesTypes = (RULE_TYPE_LIST, RULE_TYPE_LISTS, RULE_TYPE_SIMPLE, RULE_TYPE_REGEXP, RULE_TYPE_NETWORK)
+
+    DEFAULT_TARGET_PROCESS = 0
+    ACTION_DENY_IDX = 0
+    ACTION_ALLOW_IDX = 1
+    ACTION_REJECT_IDX = 2
+
+    # don't translate
+    ACTION_ALLOW = "allow"
+    ACTION_DENY = "deny"
+    ACTION_REJECT = "reject"
+    ACTION_ACCEPT = "accept"
+    ACTION_DROP = "drop"
+    ACTION_JUMP = "jump"
+    ACTION_REDIRECT = "redirect"
+    ACTION_RETURN = "return"
+    ACTION_TPROXY = "tproxy"
+    ACTION_SNAT = "snat"
+    ACTION_DNAT = "dnat"
+    ACTION_MASQUERADE = "masquerade"
+    ACTION_QUEUE = "queue"
+    ACTION_LOG = "log"
+    ACTION_STOP = "stop"
+
+    DURATION_FIELD = "duration"
+    DURATION_UNTIL_RESTART = "until restart"
+    DURATION_ALWAYS = "always"
+    DURATION_ONCE = "once"
+    DURATION_1h = "1h"
+    DURATION_30m = "30m"
+    DURATION_15m = "15m"
+    DURATION_5m = "5m"
+    DURATION_30s = "30s"
+
+    # Rules of this list are ignored/deleted
+    RULES_DURATION_FILTER = ()
+    # Rules of this list are active
+    RULES_ACTIVE_TEMPORARY_RULES = ()
+    RULES_TEMPORARY_LIST = [
+        DURATION_ONCE, DURATION_30s, DURATION_5m,
+        DURATION_15m, DURATION_30m, DURATION_1h,
+        DURATION_UNTIL_RESTART]
+
+    DEFAULT_DURATION_IDX = 6 # until restart
+
+    POPUP_CENTER = 0
+    POPUP_TOP_RIGHT = 1
+    POPUP_BOTTOM_RIGHT = 2
+    POPUP_TOP_LEFT = 3
+    POPUP_BOTTOM_LEFT = 4
+
+    DEFAULT_THEME = "global/theme"
+    DEFAULT_THEME_DENSITY_SCALE = "global/theme_density_scale"
+    DEFAULT_LANGUAGE = "global/language"
+    DEFAULT_LANGNAME = "global/langname"
+    DEFAULT_DISABLE_POPUPS = "global/disable_popups"
+    DEFAULT_TIMEOUT_KEY  = "global/default_timeout"
+    DEFAULT_ACTION_KEY   = "global/default_action"
+    DEFAULT_DURATION_KEY = "global/default_duration"
+    DEFAULT_TARGET_KEY   = "global/default_target"
+    DEFAULT_IGNORE_RULES = "global/default_ignore_rules"
+    DEFAULT_IGNORE_TEMPORARY_RULES = "global/default_ignore_temporary_rules"
+    DEFAULT_POPUP_POSITION = "global/default_popup_position"
+    DEFAULT_POPUP_ADVANCED = "global/default_popup_advanced"
+    DEFAULT_POPUP_ADVANCED_DSTIP = "global/default_popup_advanced_dstip"
+    DEFAULT_POPUP_ADVANCED_DSTPORT = "global/default_popup_advanced_dstport"
+    DEFAULT_POPUP_ADVANCED_UID = "global/default_popup_advanced_uid"
+    DEFAULT_SERVER_ADDR  = "global/server_address"
+    DEFAULT_SERVER_MAX_MESSAGE_LENGTH  = "global/server_max_message_length"
+    DEFAULT_HIDE_SYSTRAY_WARN  = "global/hide_systray_warning"
+    DEFAULT_DB_TYPE_KEY       = "database/type"
+    DEFAULT_DB_FILE_KEY       = "database/file"
+    DEFAULT_DB_PURGE_OLDEST   = "database/purge_oldest"
+    DEFAULT_DB_MAX_DAYS       = "database/max_days"
+    DEFAULT_DB_PURGE_INTERVAL = "database/purge_interval"
+    DEFAULT_DB_JRNL_WAL       = "database/jrnl_wal"
+
+    DEFAULT_TIMEOUT = 30
+
+    NOTIFICATIONS_ENABLED = "notifications/enabled"
+    NOTIFICATIONS_TYPE = "notifications/type"
+    NOTIFICATION_TYPE_SYSTEM = 0
+    NOTIFICATION_TYPE_QT = 1
+
+    STATS_REFRESH_INTERVAL = "statsDialog/refresh_interval"
+    STATS_GEOMETRY = "statsDialog/geometry"
+    STATS_LAST_TAB = "statsDialog/last_tab"
+    STATS_FILTER_TEXT = "statsDialog/general_filter_text"
+    STATS_FILTER_ACTION = "statsDialog/general_filter_action"
+    STATS_LIMIT_RESULTS = "statsDialog/general_limit_results"
+    STATS_SHOW_COLUMNS = "statsDialog/show_columns"
+    STATS_NODES_COL_STATE = "statsDialog/nodes_columns_state"
+    STATS_GENERAL_COL_STATE = "statsDialog/general_columns_state"
+    STATS_GENERAL_FILTER_TEXT = "statsDialog/"
+    STATS_GENERAL_FILTER_ACTION = "statsDialog/"
+    STATS_RULES_COL_STATE = "statsDialog/rules_columns_state"
+    STATS_FW_COL_STATE = "statsDialog/firewall_columns_state"
+    STATS_RULES_TREE_EXPANDED_0 = "statsDialog/rules_tree_0_expanded"
+    STATS_RULES_TREE_EXPANDED_1 = "statsDialog/rules_tree_1_expanded"
+    STATS_RULES_SPLITTER_POS = "statsDialog/rules_splitter_pos"
+    STATS_VIEW_COL_STATE =  "statsDialog/view_columns_state"
+    STATS_VIEW_DETAILS_COL_STATE =  "statsDialog/view_details_columns_state"
+
+    QT_PLATFORM_PLUGIN = "global/qt_platform_plugin"
+    QT_AUTO_SCREEN_SCALE_FACTOR = "global/screen_scale_factor_auto"
+    QT_SCREEN_SCALE_FACTOR = "global/screen_scale_factor"
+
+    INFOWIN_GEOMETRY = "infoWindow/geometry"
+
+    AUTH_TYPE = "auth/type"
+    AUTH_CA_CERT = "auth/cacert"
+    AUTH_CERT = "auth/cert"
+    AUTH_CERTKEY = "auth/certkey"
+    # don't translate
+
+    @staticmethod
+    def init():
+        Config.__instance = Config()
+        return Config.__instance
+
+    @staticmethod
+    def get():
+        if Config.__instance == None:
+            Config._instance = Config()
+        return Config.__instance
+
+    def __init__(self):
+        self.settings = QtCore.QSettings("opensnitch", "settings")
+
+        if self.settings.value(self.DEFAULT_TIMEOUT_KEY) == None:
+            self.setSettings(self.DEFAULT_TIMEOUT_KEY, self.DEFAULT_TIMEOUT)
+        if self.settings.value(self.DEFAULT_ACTION_KEY) == None:
+            self.setSettings(self.DEFAULT_ACTION_KEY, self.ACTION_DENY_IDX)
+        if self.settings.value(self.DEFAULT_DURATION_KEY) == None:
+            self.setSettings(self.DEFAULT_DURATION_KEY, self.DEFAULT_DURATION_IDX)
+        if self.settings.value(self.DEFAULT_TARGET_KEY) == None:
+            self.setSettings(self.DEFAULT_TARGET_KEY, self.DEFAULT_TARGET_PROCESS)
+        if self.settings.value(self.DEFAULT_DB_TYPE_KEY) == None:
+            self.setSettings(self.DEFAULT_DB_TYPE_KEY, Database.DB_TYPE_MEMORY)
+            self.setSettings(self.DEFAULT_DB_FILE_KEY, Database.DB_IN_MEMORY)
+            self.setSettings(self.DEFAULT_DB_JRNL_WAL, Database.DB_JRNL_WAL)
+
+        self.setRulesDurationFilter(
+            self.getBool(self.DEFAULT_IGNORE_RULES),
+            self.getInt(self.DEFAULT_IGNORE_TEMPORARY_RULES)
+        )
+
+    def reload(self):
+        self.settings = QtCore.QSettings("opensnitch", "settings")
+
+    def hasKey(self, key):
+        return self.settings.contains(key)
+
+    def setSettings(self, path, value):
+        self.settings.setValue(path, value)
+        self.settings.sync()
+
+    def getSettings(self, path):
+        return self.settings.value(path)
+
+    def getBool(self, path, default_value=False):
+        return self.settings.value(path, type=bool, defaultValue=default_value)
+
+    def getInt(self, path, default_value=0):
+        try:
+            return self.settings.value(path, type=int, defaultValue=default_value)
+        except Exception:
+            return default_value
+
+    def getDefaultAction(self):
+        _default_action = self.getInt(self.DEFAULT_ACTION_KEY)
+        if _default_action == self.ACTION_ALLOW_IDX:
+            return self.ACTION_ALLOW
+        else:
+            return self.ACTION_DENY
+
+    def setRulesDurationFilter(self, ignore_temporary_rules=False, temp_rules=1):
+        try:
+            if ignore_temporary_rules:
+                Config.RULES_DURATION_FILTER = [
+                    Config.DURATION_ONCE, Config.DURATION_30s, Config.DURATION_5m,
+                    Config.DURATION_15m, Config.DURATION_30m, Config.DURATION_1h,
+                    Config.DURATION_UNTIL_RESTART]
+
+                Config.RULES_DURATION_FILTER = [
+                    rule for rule in Config.RULES_TEMPORARY_LIST
+                    if Config.RULES_TEMPORARY_LIST.index(rule) < temp_rules
+                ]
+                Config.RULES_ACTIVE_TEMPORARY_RULES = [
+                    rule for rule in Config.RULES_TEMPORARY_LIST
+                    if Config.RULES_TEMPORARY_LIST.index(rule) >= temp_rules
+                ]
+                #print("Temp rules preserved (RULES_DURATION_FILTER):", Config.RULES_DURATION_FILTER)
+                #print("Temp rules to delete (ACTIVE_TEMPORARY_RULES):", Config.RULES_ACTIVE_TEMPORARY_RULES)
+
+            else:
+                Config.RULES_DURATION_FILTER = []
+        except Exception as e:
+            print("setRulesDurationFilter() exception:", e)
+
+    def getMaxMsgLength(self):
+        """return maximum configured length for the gRPC channel.
+        Default size is 4MB, but in some scenarios it's not enough.
+        """
+        maxmsglen = 4194304
+        maxmsglencfg = self.getSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH)
+        if maxmsglencfg == '4MiB':
+            maxmsglen = 4194304
+        elif maxmsglencfg == '8MiB':
+            maxmsglen = 8388608
+        elif maxmsglencfg == '16MiB':
+            maxmsglen = 16777216
+
+        print("gRPC Max Message Length:", maxmsglencfg)
+        print("                  Bytes:", maxmsglen)
+
+        return maxmsglen
diff --git a/ui/opensnitch/customwidgets/__init__.py b/ui/opensnitch/customwidgets/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ui/opensnitch/customwidgets/addresstablemodel.py b/ui/opensnitch/customwidgets/addresstablemodel.py
new file mode 100644 (file)
index 0000000..7873e5a
--- /dev/null
@@ -0,0 +1,63 @@
+
+from PyQt5.QtSql import QSqlQuery
+
+from opensnitch.utils import AsnDB
+from opensnitch.customwidgets.generictableview import GenericTableModel
+from PyQt5.QtCore import QCoreApplication as QC
+
+class AddressTableModel(GenericTableModel):
+
+    def __init__(self, tableName, headerLabels):
+        super().__init__(tableName, headerLabels)
+        self.asndb = AsnDB.instance()
+        self.reconfigureColumns()
+
+    def reconfigureColumns(self):
+        self.headerLabels = []
+        self.setHorizontalHeaderLabels(self.headerLabels)
+        self.headerLabels.append(QC.translate("stats", "What", ""))
+        self.headerLabels.append(QC.translate("stats", "Hits", ""))
+        self.headerLabels.append(QC.translate("stats", "Network name", ""))
+        self.setHorizontalHeaderLabels(self.headerLabels)
+        self.setColumnCount(len(self.headerLabels))
+        self.lastColumnCount = len(self.headerLabels)
+
+    def setQuery(self, q, db):
+        self.origQueryStr = q
+        self.db = db
+
+        if self.prevQueryStr != self.origQueryStr:
+            self.realQuery = QSqlQuery(q, db)
+
+        self.realQuery.exec_()
+        self.realQuery.last()
+
+        queryRows = max(0, self.realQuery.at()+1)
+        self.totalRowCount = queryRows
+        self.setRowCount(self.totalRowCount)
+
+        queryColumns = self.realQuery.record().count()
+        if self.asndb.is_available() and queryColumns < 3:
+            self.reconfigureColumns()
+        else:
+            # update view's columns
+            if queryColumns != self.lastColumnCount:
+                self.setModelColumns(queryColumns)
+
+        self.prevQueryStr = self.origQueryStr
+        self.rowCountChanged.emit()
+
+    def fillVisibleRows(self, q, upperBound, force=False):
+        super().fillVisibleRows(q, upperBound, force)
+
+        if self.asndb.is_available() == True and self.columnCount() <= 3:
+            for n, col in enumerate(self.items):
+                try:
+                    if len(col) < 2:
+                        continue
+                    col[2] = self.asndb.get_asn(col[0])
+                except:
+                    col[2] = ""
+                finally:
+                    self.items[n] = col
+            self.lastItems = self.items
diff --git a/ui/opensnitch/customwidgets/colorizeddelegate.py b/ui/opensnitch/customwidgets/colorizeddelegate.py
new file mode 100644 (file)
index 0000000..7b75046
--- /dev/null
@@ -0,0 +1,84 @@
+from PyQt5 import Qt, QtCore
+from PyQt5.QtWidgets import QApplication
+
+# PyQt5 >= v5.15.8 (28/01/2023) (#821)
+if hasattr(Qt, 'QItemDelegate'):
+    from PyQt5.Qt import QItemDelegate, QStyleOptionViewItem
+else:
+    from PyQt5.QtWidgets import QItemDelegate, QStyleOptionViewItem
+
+class ColorizedDelegate(QItemDelegate):
+    HMARGIN = 0
+    VMARGIN = 1
+
+    def __init__(self, parent=None, *args, actions={}):
+        QItemDelegate.__init__(self, parent, *args)
+        self._actions = actions
+        self.modelColumns = parent.model().columnCount()
+        self._style = QApplication.style()
+
+    def setConfig(self, actions):
+        self._actions = actions
+
+    #@profile_each_line
+    def paint(self, painter, option, index):
+        """Override default widget style to personalize it with our own.
+        """
+        if self._actions.get('actions') == None:
+            return super().paint(painter, option, index)
+        if not index.isValid():
+            return super().paint(painter, option, index)
+        cellValue = index.data(QtCore.Qt.DisplayRole)
+        if cellValue == None:
+            return super().paint(painter, option, index)
+
+        # initialize new QStyleOptionViewItem with the default options of this
+        # cell.
+        option = QStyleOptionViewItem(option)
+
+        # by default use item's default attributes.
+        # if we modify any of them, set it to False
+        nocolor=True
+
+        # don't call these functions in for-loops
+        cellRect = QtCore.QRect(option.rect)
+        curColumn = index.column()
+        curRow = index.row()
+        cellAlignment = option.displayAlignment
+        defaultPen = painter.pen()
+        defaultBrush = painter.brush()
+
+        self._style = QApplication.style()
+        # get default margins in order to respect them.
+        # option.widget is the QTableView
+        hmargin = self._style.pixelMetric(
+            self._style.PM_FocusFrameHMargin, None, option.widget
+        ) + 1
+        vmargin = self._style.pixelMetric(
+            self._style.PM_FocusFrameVMargin, None, option.widget
+        ) + 1
+
+        # set default margins for this cell
+        cellRect.adjust(hmargin, vmargin, -painter.pen().width(), -painter.pen().width())
+
+        for a in self._actions['actions']:
+            action = self._actions['actions'][a]
+            modified = action.run(
+                             (painter,
+                              option,
+                              index,
+                              self._style,
+                              self.modelColumns,
+                              curRow,
+                              curColumn,
+                              defaultPen,
+                              defaultBrush,
+                              cellAlignment,
+                              cellRect,
+                              cellValue)
+                             )
+            if modified[0]:
+                nocolor=False
+
+        if nocolor:
+            super().paint(painter, option, index)
diff --git a/ui/opensnitch/customwidgets/firewalltableview.py b/ui/opensnitch/customwidgets/firewalltableview.py
new file mode 100644 (file)
index 0000000..5acdba5
--- /dev/null
@@ -0,0 +1,326 @@
+
+from PyQt5 import QtCore
+from PyQt5.QtGui import QStandardItemModel, QStandardItem
+from PyQt5.QtSql import QSqlQuery, QSqlError
+from PyQt5.QtWidgets import QTableView, QAbstractSlider, QItemDelegate, QAbstractItemView, QPushButton, QWidget, QVBoxLayout
+from PyQt5.QtCore import pyqtSignal
+from PyQt5.QtCore import QCoreApplication as QC
+
+from opensnitch.nodes import Nodes
+from opensnitch.firewall import Firewall
+from opensnitch.customwidgets.updownbtndelegate import UpDownButtonDelegate
+
+class FirewallTableModel(QStandardItemModel):
+    rowCountChanged = pyqtSignal()
+    columnCountChanged = pyqtSignal(int)
+    rowsUpdated = pyqtSignal(int, tuple)
+    rowsReordered = pyqtSignal(int, str, str, int, int) # filter, addr, key, old_pos, new_pos
+
+    tableName = ""
+    # total row count which must de displayed in the view
+    totalRowCount = 0
+    # last column count to compare against with
+    lastColumnCount = 0
+
+    FILTER_ALL = 0
+    FILTER_BY_NODE = 1
+    FILTER_BY_TABLE = 2
+    FILTER_BY_CHAIN = 3
+    FILTER_BY_QUERY = 4
+    activeFilter = FILTER_ALL
+
+    UP_BTN = -1
+    DOWN_BTN = 1
+
+    COL_BTNS = 0
+    COL_UUID = 1
+    COL_ADDR = 2
+    COL_CHAIN_NAME = 3
+    COL_CHAIN_TABLE = 4
+    COL_CHAIN_FAMILY = 5
+    COL_CHAIN_HOOK = 6
+    COL_ENABLED = 7
+    COL_DESCRIPTION = 8
+    COL_PARMS = 9
+    COL_ACTION = 10
+    COL_ACTION_PARMS = 11
+
+    headersAll = [
+        "", # buttons
+        "", # uuid
+        QC.translate("firewall", "Node", ""),
+        QC.translate("firewall", "Name", ""),
+        QC.translate("firewall", "Table", ""),
+        QC.translate("firewall", "Family", ""),
+        QC.translate("firewall", "Hook", ""),
+        QC.translate("firewall", "Enabled", ""),
+        QC.translate("firewall", "Description", ""),
+        QC.translate("firewall", "Parameters", ""),
+        QC.translate("firewall", "Action", ""),
+        QC.translate("firewall", "ActionParms", ""),
+    ]
+
+    items = []
+    lastRules = []
+    position = 0
+
+    def __init__(self, tableName):
+        self.tableName = tableName
+        self._nodes = Nodes.instance()
+        self._fw = Firewall.instance()
+        self.lastColumnCount = len(self.headersAll)
+        self.lastQueryArgs = ()
+
+        QStandardItemModel.__init__(self, 0, self.lastColumnCount)
+        self.setHorizontalHeaderLabels(self.headersAll)
+
+    def filterByNode(self, addr):
+        self.activeFilter = self.FILTER_BY_NODE
+        self.fillVisibleRows(0, True, addr)
+
+    def filterAll(self):
+        self.activeFilter = self.FILTER_ALL
+        self.fillVisibleRows(0, True)
+
+    def filterByTable(self, addr, name, family):
+        self.activeFilter = self.FILTER_BY_TABLE
+        self.fillVisibleRows(0, True, addr, name, family)
+
+    def filterByChain(self, addr, table, family, chain, hook):
+        self.activeFilter = self.FILTER_BY_CHAIN
+        self.fillVisibleRows(0, True, addr, table, family, chain, hook)
+
+    def filterByQuery(self, query):
+        self.activeFilter = self.FILTER_BY_QUERY
+        self.fillVisibleRows(0, True, query)
+
+    def reorderRows(self, action, row):
+        if (row.row()+action == self.rowCount() and action == self.DOWN_BTN) or \
+                (row.row() == 0 and action == self.UP_BTN):
+            return
+
+        # XXX: better use moveRow()?
+        newRow = []
+        # save the row we're about to overwrite
+        for c in range(self.columnCount()):
+            item = self.index(row.row()+action, c)
+            itemText = item.data()
+            newRow.append(itemText)
+        # overwrite next item with current data
+        for c in range(self.columnCount()):
+            curItem = self.index(row.row(), c).data()
+            nextIdx = self.index(row.row()+action, c)
+            self.setData(nextIdx, curItem, QtCore.Qt.DisplayRole)
+        # restore row with the overwritten data
+        for i, nr in enumerate(newRow):
+            idx = self.index(row.row(), i)
+            self.setData(idx, nr, QtCore.Qt.DisplayRole)
+
+        self.rowsReordered.emit(
+            self.activeFilter,
+            self.index(row.row()+action, self.COL_ADDR).data(), # address
+            self.index(row.row()+action, self.COL_UUID).data(), # key
+            row.row(),
+            row.row()+action)
+
+    def refresh(self, force=False):
+        self.fillVisibleRows(0, force, *self.lastQueryArgs)
+
+    #Some QSqlQueryModel methods must be mimiced so that this class can serve as a drop-in replacement
+    #mimic QSqlQueryModel.query()
+    def query(self):
+        return self
+
+    #mimic QSqlQueryModel.query().lastError()
+    def lastError(self):
+        return QSqlError()
+
+    #mimic QSqlQueryModel.clear()
+    def clear(self):
+        self.items = []
+        self.removeColumns(0, self.lastColumnCount)
+        self.setColumnCount(0)
+        self.setRowCount(0)
+
+    # set columns based on query's fields
+    def setModelColumns(self, headers):
+        count = len(headers)
+        self.clear()
+        self.setHorizontalHeaderLabels(headers)
+        self.lastColumnCount = count
+        self.setColumnCount(self.lastColumnCount)
+        self.columnCountChanged.emit(count)
+
+    def query(self):
+        return QSqlQuery()
+
+    def setQuery(self, q, db, args=None):
+        self.refresh()
+
+    def nextRecord(self, offset):
+        self.position += 1
+
+    def prevRecord(self, offset):
+        self.position -= 1
+
+    def fillVisibleRows(self, upperBound, force, *data):
+        if self.activeFilter == self.FILTER_BY_NODE and len(data) == 0:
+                return
+
+        cols = []
+        rules = []
+        #don't trigger setItem's signals for each cell, instead emit dataChanged for all cells
+        self.blockSignals(True)
+        # mandatory for rows refreshing
+        self.layoutAboutToBeChanged.emit()
+
+        if self.activeFilter == self.FILTER_BY_NODE:
+            rules = self._fw.get_node_rules(data[0])
+            self.setModelColumns(self.headersAll)
+        elif self.activeFilter == self.FILTER_BY_TABLE:
+            rules = self._fw.filter_by_table(data[0], data[1], data[2])
+            self.setModelColumns(self.headersAll)
+        elif self.activeFilter == self.FILTER_BY_CHAIN:
+            rules = self._fw.filter_by_chain(data[0], data[1], data[2], data[3], data[4])
+            self.setModelColumns(self.headersAll)
+        elif self.activeFilter == self.FILTER_BY_QUERY:
+            rules = self._fw.filter_rules(data[0])
+            self.setModelColumns(self.headersAll)
+        else:
+            self.setModelColumns(self.headersAll)
+            rules = self._fw.get_rules()
+
+        self.addRows(rules)
+
+        self.blockSignals(False)
+        if self.lastRules != rules or force == True:
+            self.layoutChanged.emit()
+            self.totalRowCount = len(rules)
+            self.setRowCount(self.totalRowCount)
+            self.rowsUpdated.emit(self.activeFilter, data)
+            self.dataChanged.emit(self.createIndex(0,0), self.createIndex(self.rowCount(), self.columnCount()))
+
+        self.lastRules = rules
+        self.lastQueryArgs = data
+        del cols
+        del rules
+
+    def addRows(self, rules):
+        self.items = []
+        for rows in rules:
+            cols = []
+            cols.append(QStandardItem("")) # buttons column
+            for cl in rows:
+                item = QStandardItem(cl)
+                item.setData(cl, QtCore.Qt.UserRole+1)
+                cols.append(item)
+            self.appendRow(cols)
+
+    def dumpRows(self):
+        for rule in self.lastRules:
+            print(rule)
+
+class FirewallTableView(QTableView):
+    # how many rows can potentially be displayed in viewport
+    # the actual number of rows currently displayed may be less than this
+    maxRowsInViewport = 0
+    rowsReordered = pyqtSignal(str) # addr
+
+    def __init__(self, parent):
+        QTableView.__init__(self, parent)
+        self._fw = Firewall.instance()
+        self._fw.rules.rulesUpdated.connect(self._cb_fw_rules_updated)
+
+        self.verticalHeader().setVisible(True)
+        self.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignCenter)
+        self.horizontalHeader().setStretchLastSection(True)
+
+        # FIXME: if the firewall being used is iptables, hide the column to
+        # reorder rules, it's not supported.
+        updownBtn = UpDownButtonDelegate(self)
+        self.setItemDelegateForColumn(0, updownBtn)
+        updownBtn.clicked.connect(self._cb_fw_rule_position_changed)
+
+    def _cb_fw_rules_updated(self):
+        self.model().refresh(True)
+
+    def _cb_column_count_changed(self, num):
+        for i in range(num):
+            self.resizeColumnToContents(i)
+
+    def _cb_fw_rule_position_changed(self, action, row):
+        self.model().reorderRows(action, row)
+
+    def _cb_rows_reordered(self, view, node_addr, uuid, old_pos, new_pos):
+        if self._fw.swap_rules(view, node_addr, uuid, old_pos, new_pos):
+            self.rowsReordered.emit(node_addr)
+
+    #@QtCore.pyqtSlot(int, tuple)
+    def _cb_rows_updated(self, view, data):
+        for c in range(self.model().rowCount()):
+            self.setColumnHidden(c, False)
+            #self.horizontalHeader().setSectionResizeMode(
+            #    c, QHeaderView.ResizeToContents
+            #)
+
+        self.setColumnHidden(FirewallTableModel.COL_BTNS, True)
+        self.setColumnHidden(FirewallTableModel.COL_UUID, True)
+        if view >= self.model().FILTER_BY_NODE:
+            # hide address column
+            self.setColumnHidden(FirewallTableModel.COL_ADDR, True)
+        if view >= self.model().FILTER_BY_TABLE:
+            self.setColumnHidden(FirewallTableModel.COL_CHAIN_TABLE, True)
+            self.setColumnHidden(FirewallTableModel.COL_CHAIN_FAMILY, True)
+        if view >= self.model().FILTER_BY_CHAIN:
+            # hide chain's name, family and hook
+            self.setColumnHidden(FirewallTableModel.COL_CHAIN_NAME, True)
+            self.setColumnHidden(FirewallTableModel.COL_CHAIN_HOOK, True)
+            self.setColumnHidden(FirewallTableModel.COL_BTNS, False)
+
+    def filterAll(self):
+        self.model().filterAll()
+
+    def filterByNode(self, addr):
+        self.model().filterByNode(addr)
+
+    def filterByTable(self, addr, name, family):
+        self.model().filterByTable(addr, name, family)
+
+    def filterByChain(self, addr, table, family, chain, hook):
+        self.model().filterByChain(addr, table, family, chain, hook)
+
+    def filterByQuery(self, query):
+        self.model().filterByQuery(query)
+
+    def refresh(self):
+        self.model().refresh(True)
+
+    def clearSelection(self):
+        pass
+
+    def copySelection(self):
+        selection = self.selectedIndexes()
+        if not selection:
+            return None
+        rows = []
+        row = []
+        lastRow = 0
+        for idx in selection:
+            if idx.row() == lastRow:
+                row.append(self.model().index(idx.row(), idx.column()).data())
+            else:
+                row = []
+                lastRow = idx.row()
+                rows.append(row)
+        return rows
+
+    def setModel(self, model):
+        super().setModel(model)
+        self.horizontalHeader().sortIndicatorChanged.disconnect()
+        self.setSortingEnabled(True)
+        self.model().columnCountChanged.connect(self._cb_column_count_changed)
+        model.rowsUpdated.connect(self._cb_rows_updated)
+        model.rowsReordered.connect(self._cb_rows_reordered)
+
+    def setTrackingColumn(self, col):
+        pass
diff --git a/ui/opensnitch/customwidgets/generictableview.py b/ui/opensnitch/customwidgets/generictableview.py
new file mode 100644 (file)
index 0000000..fb6482b
--- /dev/null
@@ -0,0 +1,461 @@
+from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem
+from PyQt5.QtSql import QSqlQueryModel, QSqlQuery, QSql
+from PyQt5.QtWidgets import QTableView, QAbstractSlider
+from PyQt5.QtCore import QItemSelectionModel, pyqtSignal, QEvent, Qt
+import time
+import math
+
+from PyQt5.QtCore import QCoreApplication as QC
+
+class GenericTableModel(QStandardItemModel):
+    rowCountChanged = pyqtSignal()
+    beginViewPortRefresh = pyqtSignal()
+    endViewPortRefresh = pyqtSignal()
+
+    db = None
+    tableName = ""
+    # total row count which must de displayed in the view
+    totalRowCount = 0
+    #
+    lastColumnCount = 0
+
+    # original query string before we modify it
+    origQueryStr = QSqlQuery()
+    # previous original query string; used to check if the query has changed
+    prevQueryStr = ''
+    # modified query object
+    realQuery = QSqlQuery()
+
+    items = []
+    lastItems = []
+
+    def __init__(self, tableName, headerLabels):
+        self.tableName = tableName
+        self.headerLabels = headerLabels
+        self.lastColumnCount = len(self.headerLabels)
+        QStandardItemModel.__init__(self, 0, self.lastColumnCount)
+        self.setHorizontalHeaderLabels(self.headerLabels)
+
+    #Some QSqlQueryModel methods must be mimiced so that this class can serve as a drop-in replacement
+    #mimic QSqlQueryModel.query()
+    def query(self):
+        return self
+
+    #mimic QSqlQueryModel.query().lastQuery()
+    def lastQuery(self):
+        return self.origQueryStr
+
+    #mimic QSqlQueryModel.query().lastError()
+    def lastError(self):
+        return self.realQuery.lastError()
+
+    #mimic QSqlQueryModel.clear()
+    def clear(self):
+        pass
+
+    def rowCount(self, index=None):
+        """ensures that only the needed rows is created"""
+        return len(self.items)
+
+    def data(self, index, role=Qt.DisplayRole):
+        """Paint rows with the data stored in self.items
+        """
+        if role == Qt.DisplayRole or role == Qt.EditRole:
+            items_count = len(self.items)
+            if index.isValid() and items_count > 0 and index.row() < items_count:
+                return self.items[index.row()][index.column()]
+        return QStandardItemModel.data(self, index, role)
+
+    # set columns based on query's fields
+    def setModelColumns(self, newColumns):
+        # Avoid firing signals while reconfiguring the view, it causes
+        # segfaults.
+        self.blockSignals(True);
+
+        self.headerLabels = []
+        self.removeColumns(0, self.lastColumnCount)
+        self.setHorizontalHeaderLabels(self.headerLabels)
+        for col in range(0, newColumns):
+            self.headerLabels.append(self.realQuery.record().fieldName(col))
+        self.lastColumnCount = newColumns
+        self.setHorizontalHeaderLabels(self.headerLabels)
+        self.setColumnCount(len(self.headerLabels))
+
+        self.blockSignals(False);
+
+    def setQuery(self, q, db):
+        self.origQueryStr = q
+        self.db = db
+        #print("q:", q)
+
+        if self.prevQueryStr != self.origQueryStr:
+            self.realQuery = QSqlQuery(q, db)
+
+        self.realQuery.exec_()
+        self.realQuery.last()
+
+        queryRows = max(0, self.realQuery.at()+1)
+        self.totalRowCount = queryRows
+        self.setRowCount(self.totalRowCount)
+
+        # update view's columns
+        queryColumns = self.realQuery.record().count()
+        if queryColumns != self.lastColumnCount:
+            self.setModelColumns(queryColumns)
+
+        self.prevQueryStr = self.origQueryStr
+        self.rowCountChanged.emit()
+
+    def nextRecord(self, offset):
+        cur_pos = self.realQuery.at()
+        q.seek(max(cur_pos, cur_pos+offset))
+
+    def prevRecord(self, offset):
+        cur_pos = self.realQuery.at()
+        q.seek(min(cur_pos, cur_pos-offset))
+
+    def refreshViewport(self, scrollValue, maxRowsInViewport, force=False):
+        """Refresh the viewport with data from the db.
+        Before making any changes, emit a signal which will perform several operations
+        (save current selected row, etc).
+        force var will force a refresh if the scrollbar is at the top or bottom of the
+        viewport, otherwise skip it to allow rows analyzing without refreshing.
+        """
+        if not force:
+            return
+
+        self.beginViewPortRefresh.emit()
+        # set records position to last, in order to get correctly the number of
+        # rows.
+        self.realQuery.last()
+        rowsFound = max(0, self.realQuery.at()+1)
+        if scrollValue == 0 or self.realQuery.at() == QSql.BeforeFirstRow:
+            self.realQuery.seek(QSql.BeforeFirstRow)
+        elif self.realQuery.at() == QSql.AfterLastRow:
+            self.realQuery.seek(rowsFound - maxRowsInViewport)
+        else:
+            self.realQuery.seek(min(scrollValue-1, self.realQuery.at()))
+
+        upperBound = min(maxRowsInViewport, rowsFound)
+        self.setRowCount(self.totalRowCount)
+
+        # only visible rows will be filled with data, and only if we're not
+        # updating the viewport already.
+        if force and (upperBound > 0 or self.realQuery.at() < 0):
+            self.fillVisibleRows(self.realQuery, upperBound, force)
+        self.endViewPortRefresh.emit()
+
+    def fillVisibleRows(self, q, upperBound, force=False):
+        rowsLabels = []
+        self.setVerticalHeaderLabels(rowsLabels)
+
+        self.items = []
+        cols = []
+        #don't trigger setItem's signals for each cell, instead emit dataChanged for all cells
+        for x in range(0, upperBound):
+            q.next()
+            if q.at() < 0:
+                # if we don't set query to a valid record here, it gets stucked
+                # forever at -2/-1.
+                q.seek(upperBound)
+                break
+            rowsLabels.append(str(q.at()+1))
+            cols = []
+            for col in range(0, len(self.headerLabels)):
+                cols.append(str(q.value(col)))
+
+            self.items.append(cols)
+
+        self.setVerticalHeaderLabels(rowsLabels)
+        if self.lastItems != self.items or force == True:
+            self.dataChanged.emit(self.createIndex(0,0), self.createIndex(upperBound, len(self.headerLabels)))
+        self.lastItems = self.items
+        del cols
+
+    def dumpRows(self):
+        rows = []
+        q = QSqlQuery(self.db)
+        q.exec(self.origQueryStr)
+        q.seek(QSql.BeforeFirstRow)
+        while True:
+            q.next()
+            if q.at() == QSql.AfterLastRow:
+                break
+            row = []
+            for col in range(0, len(self.headerLabels)):
+                row.append(q.value(col))
+            rows.append(row)
+        return rows
+
+    def copySelectedRows(self, start=QSql.BeforeFirstRow, end=QSql.AfterLastRow):
+        rows = []
+        lastAt = self.realQuery.at()
+        self.realQuery.seek(start)
+        while True:
+            self.realQuery.next()
+            if self.realQuery.at() == QSql.AfterLastRow or len(rows) >= end:
+                break
+            row = []
+            for col in range(0, len(self.headerLabels)):
+                row.append(self.realQuery.value(col))
+            rows.append(row)
+        self.realQuery.seek(lastAt)
+        return rows
+
+class GenericTableView(QTableView):
+    # how many rows can potentially be displayed in viewport
+    # the actual number of rows currently displayed may be less than this
+    maxRowsInViewport = 0
+    vScrollBar = None
+    curSelection = None
+    trackingCol = 0
+
+    def __init__(self, parent):
+        QTableView.__init__(self, parent)
+        self.mousePressed = False
+
+        #eventFilter to catch key up/down events and wheel events
+        self.verticalHeader().setVisible(True)
+        self.horizontalHeader().setDefaultAlignment(Qt.AlignCenter)
+        self.horizontalHeader().setStretchLastSection(True)
+        #the built-in vertical scrollBar of this view is always off
+        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
+        self.installEventFilter(self)
+
+    def setVerticalScrollBar(self, vScrollBar):
+        self.vScrollBar = vScrollBar
+        self.vScrollBar.valueChanged.connect(self.onScrollbarValueChanged)
+        self.vScrollBar.setVisible(False)
+
+    def setModel(self, model):
+        super().setModel(model)
+        model.rowCountChanged.connect(self.onRowCountChanged)
+        model.beginViewPortRefresh.connect(self.onBeginViewportRefresh)
+        model.endViewPortRefresh.connect(self.onEndViewportRefresh)
+        self.horizontalHeader().sortIndicatorChanged.disconnect()
+        self.setSortingEnabled(False)
+
+    def setTrackingColumn(self, col):
+        """column used to track a selected row while scrolling"""
+        self.trackingCol = col
+
+    def clear(self):
+        pass
+
+    def refresh(self):
+        self.calculateRowsInViewport()
+        self.model().setRowCount(min(self.maxRowsInViewport, self.model().totalRowCount))
+        self.model().refreshViewport(self.vScrollBar.value(), self.maxRowsInViewport, force=True)
+
+    def forceViewRefresh(self):
+        return (self.vScrollBar.minimum() == self.vScrollBar.value() or self.vScrollBar.maximum() == self.vScrollBar.value())
+
+    def calculateRowsInViewport(self):
+        rowHeight = self.verticalHeader().defaultSectionSize()
+        #columnSize = self.horizontalHeader().defaultSectionSize()
+        # we don't want partial-height rows in viewport, hence .floor()
+        self.maxRowsInViewport = math.floor(self.viewport().height() / rowHeight)+1
+
+    def currentChanged(self, cur, prev):
+        #super().currentChanged(cur, prev)
+        if not self.mousePressed or prev.row() == cur.row():
+            return
+        maxVal = self.maxRowsInViewport-1
+        if cur.row() >= maxVal or prev.row() >= maxVal:
+            self.vScrollBar.setValue(self.vScrollBar.value() + 1)
+        elif cur.row() == 0:
+            self.vScrollBar.setValue(max(0, self.vScrollBar.value() - 1))
+
+    def mouseReleaseEvent(self, event):
+        super().mouseReleaseEvent(event)
+        self.mousePressed = False
+
+    # save the selected index, to preserve selection when moving around.
+    def mousePressEvent(self, event):
+        # we need to call upper class to paint selections properly
+        super().mousePressEvent(event)
+        if event.button() != Qt.LeftButton:
+            return
+        self.mousePressed = True
+
+        item = self.indexAt(event.pos())
+        clickedItem = self.model().index(item.row(), self.trackingCol)
+        if clickedItem.data() == None:
+            return
+
+        if item == None and self.curSelection == None:
+            return
+        elif item != None and self.curSelection == None:
+            # force selecting the row below
+            self.curSelection = ""
+        if clickedItem == None:
+            return
+
+        if clickedItem.data() == self.curSelection:
+            self.curSelection = None
+            flags = QItemSelectionModel.Rows | QItemSelectionModel.Deselect
+        else:
+            self.curSelection = clickedItem.data()
+            flags = QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent
+
+        self.selectionModel().setCurrentIndex(
+            clickedItem,
+            flags
+        )
+
+    def onBeginViewportRefresh(self):
+        # if the selected row due to scrolling up/down doesn't match with the
+        # saved index, deselect the row, because the saved index is out of the
+        # view.
+        index = self.selectionModel().selectedRows(self.trackingCol)
+        if len(index) == 0:
+            return
+        if index[0].data() != self.curSelection:
+            self.selectionModel().clear()
+
+    def onEndViewportRefresh(self):
+        self._selectSavedIndex()
+
+    def resizeEvent(self, event):
+        super().resizeEvent(event)
+        #refresh the viewport data based on new geometry
+        self.refresh()
+
+    def onRowCountChanged(self):
+        totalCount = self.model().totalRowCount
+        self.vScrollBar.setVisible(True if totalCount > self.maxRowsInViewport else False)
+
+        self.vScrollBar.setMinimum(0)
+        # we need to substract the displayed rows to the total rows, to scroll
+        # down correctly.
+        self.vScrollBar.setMaximum(max(0, totalCount - self.maxRowsInViewport+1))
+
+        self.model().refreshViewport(self.vScrollBar.value(), self.maxRowsInViewport, force=self.forceViewRefresh())
+
+    def clearSelection(self):
+        self.selectionModel().reset()
+        self.selectionModel().clearCurrentIndex()
+
+    def copySelection(self):
+        model = self.selectionModel()
+        curModel = self.model()
+        selection = model.selectedRows()
+        if not selection:
+            return None
+
+        rows = []
+        for idx in selection:
+            row = []
+            for col in range(0, curModel.columnCount()):
+                row.append(curModel.index(idx.row(), col).data())
+            rows.append(row)
+        return rows
+
+    def getCurrentIndex(self):
+        return self.selectionModel().currentIndex().internalId()
+
+    def currentSelection(self):
+        return self.curSelection
+
+    def selectItem(self, _data, _column):
+        """Select a row based on the data displayed on the given column.
+        """
+        items = self.model().findItems(_data, column=_column)
+        if len(items) > 0:
+            self.selectionModel().setCurrentIndex(
+                items[0].index(),
+                QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent
+            )
+
+    def _selectSavedIndex(self):
+        if self.curSelection == None or self.mousePressed:
+            return
+
+        items = self.model().findItems(self.curSelection, column=self.trackingCol)
+        if len(items) > 0:
+            self.selectionModel().setCurrentIndex(
+                items[0].index(),
+                QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent
+            )
+
+    def _selectLastRow(self):
+        if self.curSelection != None:
+            return
+        internalId = self.getCurrentIndex()
+        self.selectionModel().setCurrentIndex(
+            self.model().createIndex(self.maxRowsInViewport-2, self.trackingCol, internalId),
+            QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent
+        )
+
+    def _selectRow(self, pos):
+        internalId = self.getCurrentIndex()
+        self.selectionModel().setCurrentIndex(
+            self.model().createIndex(pos, self.trackingCol, internalId),
+            QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent
+        )
+
+    def onScrollbarValueChanged(self, vSBNewValue):
+        self.model().refreshViewport(vSBNewValue, self.maxRowsInViewport, force=True)
+
+    def onKeyUp(self):
+        self.curSelection = self.selectionModel().currentIndex().data()
+        if self.selectionModel().currentIndex().row() == 0:
+            self.vScrollBar.setValue(max(0, self.vScrollBar.value() - 1))
+
+    def onKeyDown(self):
+        self.curSelection = self.selectionModel().currentIndex().data()
+        if self.curSelection == None:
+            self._selectLastRow()
+            return
+
+        curRow = self.selectionModel().currentIndex().row()
+        if curRow >= self.maxRowsInViewport-2:
+            self.onKeyPageDown()
+            self._selectRow(0)
+        else:
+            self._selectRow(curRow)
+
+    def onKeyHome(self):
+        self.vScrollBar.setValue(0)
+        self.selectionModel().clear()
+
+    def onKeyEnd(self):
+        self.vScrollBar.setValue(self.vScrollBar.maximum())
+        self.selectionModel().clear()
+        self._selectLastRow()
+
+    def onKeyPageUp(self):
+        newValue = max(0, self.vScrollBar.value() - self.maxRowsInViewport)
+        self.vScrollBar.setValue(newValue)
+
+    def onKeyPageDown(self):
+        if self.vScrollBar.isVisible() == False:
+            return
+
+        newValue = self.vScrollBar.value() + (self.maxRowsInViewport-2)
+        self.vScrollBar.setValue(newValue)
+
+    def eventFilter(self, obj, event):
+        if event.type() == QEvent.KeyPress:
+            # FIXME: setValue() does not update the scrollbars correctly in
+            # some pyqt versions.
+            if event.key() == Qt.Key_Up:
+                self.onKeyUp()
+            elif event.key() == Qt.Key_Down:
+                self.onKeyDown()
+            elif event.key() == Qt.Key_Home:
+                self.onKeyHome()
+            elif event.key() == Qt.Key_End:
+                self.onKeyEnd()
+            elif event.key() == Qt.Key_PageUp:
+                self.onKeyPageUp()
+            elif event.key() == Qt.Key_PageDown:
+                self.onKeyPageDown()
+            elif event.key() == Qt.Key_Escape:
+                self.selectionModel().clear()
+                self.curSelection = None
+        elif event.type() == QEvent.Wheel:
+            self.vScrollBar.wheelEvent(event)
+            return True
+
+        return super(GenericTableView, self).eventFilter(obj, event)
diff --git a/ui/opensnitch/customwidgets/main.py b/ui/opensnitch/customwidgets/main.py
new file mode 100644 (file)
index 0000000..cb86ab0
--- /dev/null
@@ -0,0 +1,498 @@
+from PyQt5 import QtCore
+from PyQt5.QtGui import QColor, QStandardItemModel, QStandardItem
+from PyQt5.QtSql import QSqlQueryModel, QSqlQuery, QSql
+from PyQt5.QtWidgets import QTableView
+from PyQt5.QtCore import QItemSelectionModel, pyqtSignal, QEvent
+import time
+import math
+
+from PyQt5.QtCore import QCoreApplication as QC
+class ColorizedQSqlQueryModel(QSqlQueryModel):
+    """
+        model=CustomQSqlQueryModel(
+            modelData=
+                {
+                'colorize':
+                      {'offline': (QColor(QtCore.Qt.red), 2)},
+                'alignment': { Qt.AlignLeft, 2 }
+                }
+            )
+    """
+    RED   = QColor(QtCore.Qt.red)
+    GREEN = QColor(QtCore.Qt.green)
+
+    def __init__(self, modelData={}):
+        QSqlQueryModel.__init__(self)
+        self._model_data = modelData
+
+    def data(self, index, role=QtCore.Qt.DisplayRole):
+        if not index.isValid():
+            return QSqlQueryModel.data(self, index, role)
+
+        column = index.column()
+        row = index.row()
+
+        if role == QtCore.Qt.TextAlignmentRole:
+            return QtCore.Qt.AlignCenter
+        if role == QtCore.Qt.TextColorRole:
+            for _, what in enumerate(self._model_data):
+                d = QSqlQueryModel.data(self, self.index(row, self._model_data[what][1]), QtCore.Qt.DisplayRole)
+                if column == self._model_data[what][1] and what in d:
+                    return self._model_data[what][0]
+
+        return QSqlQueryModel.data(self, index, role)
+
+class ConnectionsTableModel(QStandardItemModel):
+    rowCountChanged = pyqtSignal()
+
+    #max rowid in the db; starts with 1, not with 0
+    maxRowId = 0
+    #previous total number of rows in the db when the filter was applied
+    prevFiltRowCount = 0
+    #total number of rows in the db when the filter was not applied
+    prevNormRowCount = 0
+    #total row count which must de displayed in the view
+    totalRowCount = 0
+    #new rows which must be added to the top of the rows displayed in the view
+    prependedRowCount = 0
+
+    db = None
+    #original query string before we modify it
+    origQueryStr = QSqlQuery()
+    #modified query object
+    realQuery = QSqlQuery()
+    #previous original query string; used to check if the query has changed
+    prevQueryStr = ''
+    #whether or not the original query has a filter (a WHERE condition)
+    isQueryFilter = False
+    limit = None
+
+    #a map for fast lookup or rows when filter is enabled
+    #contains ranges of rowids and count of filter hits
+    #range format {'from': <rowid>, 'to': <rowid>, 'hits':<int>}
+    #including the 'from' rowid up to but NOT including the 'to' rowid
+    map = []
+    rangeSize = 1000
+    #all unique/distinct values for each column will be stored here
+    distinct = {'time':[], 'process':[], 'dst_host':[], 'dst_ip':[], 'dst_port':[], 'rule':[], 'node':[], 'protocol':[]}
+    #what was the last rowid\time when the distinct value were updates
+    distinctLastRowId = 0
+    distinctLastUpdateTime = time.time()
+
+    def __init__(self):
+        self.headerLabels = [
+            QC.translate("stats", "Time", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Node", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Action", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Destination", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Protocol", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Process", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Rule", "This is a word, without spaces and symbols.").replace(" ", ""),
+        ]
+        QStandardItemModel.__init__(self, 0, len(self.headerLabels))
+        self.setHorizontalHeaderLabels(self.headerLabels)
+
+    #Some QSqlQueryModel methods must be mimiced so that this class can serve as a drop-in replacement
+    #mimic QSqlQueryModel.query()
+    def query(self):
+        return self
+
+    #mimic QSqlQueryModel.query().lastQuery()
+    def lastQuery(self):
+        return self.origQueryStr
+
+    #mimic QSqlQueryModel.query().lastError()
+    def lastError(self):
+        return self.realQuery.lastError()
+
+    #mimic QSqlQueryModel.clear()
+    def clear(self):
+        pass
+
+    def setQuery(self, q, db):
+        self.origQueryStr = q
+        self.db = db
+        maxRowIdQuery = QSqlQuery(db)
+        maxRowIdQuery.setForwardOnly(True)
+        maxRowIdQuery.exec("SELECT MAX(rowid) FROM connections")
+        maxRowIdQuery.first()
+        value = maxRowIdQuery.value(0)
+        self.maxRowId = 0 if value == '' else int(value)
+        self.updateDistinctIfNeeded()
+        self.limit = int(q.split(' ')[-1]) if q.split(' ')[-2] == 'LIMIT' else None
+        self.isQueryFilter = True if ("LIKE '%" in q and "LIKE '% %'" not in q) or 'Action = "' in q else False
+
+        self.realQuery = QSqlQuery(db)
+        isTotalRowCountChanged = False
+        isQueryChanged = False
+        if self.prevQueryStr != q:
+            isQueryChanged = True
+        if self.isQueryFilter:
+            if isQueryChanged:
+                self.buildMap()
+            largestRowIdInMap = self.map[0]['from']
+            newRowsCount = self.maxRowId - largestRowIdInMap
+            self.prependedRowCount = 0
+
+            if newRowsCount > 0:
+                starttime = time.time()
+                self.realQuery.setForwardOnly(True)
+                for offset in range(0, newRowsCount, self.rangeSize):
+                    lowerBound = largestRowIdInMap + offset
+                    upperBound = min(lowerBound + self.rangeSize, self.maxRowId)
+                    part1, part2 = q.split('ORDER')
+                    qStr = part1 + 'AND rowid>'+ str(lowerBound) + ' AND rowid<=' + str(upperBound) + ' ORDER' + part2
+                    self.realQuery.exec(qStr)
+                    self.realQuery.last()
+                    rowsInRange = max(0, self.realQuery.at()+1)
+                    if self.map[0]['from'] - self.map[0]['to'] < self.rangeSize:
+                        #consolidate with the previous range; we don't want many small ranges
+                        self.map[0]['from'] = upperBound
+                        self.map[0]['hits'] += rowsInRange
+                    else:
+                        self.map.insert(0, {'from':upperBound, 'to':lowerBound, 'hits':rowsInRange})
+                    self.prependedRowCount += rowsInRange
+                    if time.time() - starttime > 0.5:
+                        #dont freeze the UI when fetching too many recent rows
+                        break
+
+            self.totalRowCount = 0
+            for i in self.map:
+                self.totalRowCount += i['hits']
+            if self.totalRowCount != self.prevFiltRowCount:
+                isTotalRowCountChanged = True
+                self.prevFiltRowCount = self.totalRowCount
+        else: #self.isQueryFilter == False
+            self.prependedRowCount = self.maxRowId - self.prevNormRowCount
+            self.totalRowCount = self.maxRowId
+            if self.totalRowCount != self.prevNormRowCount:
+                isTotalRowCountChanged = True
+                self.prevNormRowCount = self.totalRowCount
+
+        self.prevQueryStr = self.origQueryStr
+        if isTotalRowCountChanged or self.prependedRowCount > 0 or isQueryChanged:
+            self.rowCountChanged.emit()
+
+    #fill self.map with data
+    def buildMap(self):
+        self.map = []
+        q = QSqlQuery(self.db)
+        q.setForwardOnly(True)
+        self.updateDistinctIfNeeded(True)
+        filterStr = self.getFilterStr()
+        actionStr = self.getActionStr()
+        #we only want to know the count of matching rows
+        qStr = "SELECT COUNT(*) from connections WHERE (rowid> :lowerBound AND rowid<= :upperBound)"
+        if actionStr:
+            qStr += ' AND ' + actionStr
+        matchStr = self.getMatch(filterStr) if filterStr else None
+        if matchStr:
+            qStr += ' AND ' + matchStr
+        qStr += ' LIMIT ' + str(self.limit) if self.limit else ''
+        q.prepare(qStr)
+
+        totalRows = 0
+        for offset in range(self.maxRowId, -1, -self.rangeSize):
+            upperBound = offset
+            lowerBound = max(0, upperBound - self.rangeSize)
+            if (not filterStr and actionStr) or (filterStr and matchStr):
+                #either 1) only action was present or 2) filter which has a match (with or without action)
+                q.bindValue(":lowerBound", str(lowerBound))
+                q.bindValue(":upperBound", str(upperBound))
+                q.exec_()
+                q.first()
+                rowsInRange = int(q.value(0))
+            else:
+                rowsInRange = 0
+            totalRows += rowsInRange
+            self.map.append({'from':upperBound, 'to':lowerBound, 'hits':rowsInRange})
+            if self.limit and totalRows >= self.limit:
+                break
+
+    #periodically keep track of all distinct values for each column
+    #this is needed in order to build efficient queries when the filter is applied
+    def updateDistinctIfNeeded(self, force=False):
+        if (not force and (time.time() - self.distinctLastUpdateTime) < 10) or self.maxRowId == self.distinctLastRowId:
+            return
+        if (self.maxRowId < self.distinctLastRowId):
+            #the db has been cleared, re-init the values
+            self.distinctLastRowId = 0
+            self.distinct = {'time':[], 'process':[], 'dst_host':[], 'dst_ip':[], 'dst_port':[], 'rule':[], 'node':[], 'protocol':[]}
+        q = QSqlQuery(self.db)
+        q.setForwardOnly(True)
+        for column in self.distinct.keys():
+            q.exec('SELECT DISTINCT ' + column + ' FROM connections WHERE rowid>'
+            + str(self.distinctLastRowId) + ' AND rowid<=' + str(self.maxRowId))
+            while q.next():
+                if q.value(0) not in self.distinct[column]:
+                    self.distinct[column].append(q.value(0))
+        self.distinctLastRowId =self.maxRowId
+        self.distinctLastUpdateTime = time.time()
+
+    #refresh the viewport with data from the db
+    #"value" is vertical scrollbar's value
+    def refreshViewport(self, value, maxRowsInViewport):
+        q = QSqlQuery(self.db)
+        #sequential number of topmost/bottommost rows in viewport (numbering starts from the bottom with 1 not with 0)
+        botRowNo = max(1, self.totalRowCount - (value + maxRowsInViewport-1))
+        topRowNo = min(botRowNo + maxRowsInViewport-1, self.totalRowCount)
+
+        if not self.isQueryFilter:
+            part1, part2 = self.origQueryStr.split('ORDER')
+            qStr = part1 + 'WHERE rowid>='+ str(botRowNo) + ' AND rowid<=' + str(topRowNo) + ' ORDER' + part2
+        else:
+            self.updateDistinctIfNeeded(True)
+            #replace query part between WHERE and ORDER
+            qStr = self.origQueryStr.split('WHERE')[0] + ' WHERE '
+            actionStr = self.getActionStr()
+            if actionStr:
+                qStr += actionStr + " AND "
+            #find inside the map the range(s) in which top and bottom rows are located
+            total, offsetInRange, botRowFound, topRowFound = 0, None, False, False
+            ranges = [{'from':0, 'to':0, 'hits':0}]
+            for i in reversed(self.map):
+                if total + i['hits'] >= botRowNo:
+                    botRowFound = True
+                if total + i['hits'] >= topRowNo:
+                    topRowFound = True
+                if botRowFound and i['hits'] > 0:
+                    if i['to'] == ranges[-1]['from']:
+                        #merge two adjacent ranges
+                        ranges[-1]['from'] = i['from']
+                        ranges[-1]['hits'] += i['hits']
+                    else:
+                        ranges.append(i.copy())
+                if topRowFound:
+                    offsetInRange = i['hits'] - (topRowNo - total)
+                    break
+                total += i['hits']
+
+            rangeStr = ''
+            if len(ranges) > 0:
+                rangeStr = '('
+                for r in ranges:
+                    rangeStr += '(rowid>' + str(r['to']) + ' AND rowid<=' + str(r['from']) + ') OR '
+                rangeStr = rangeStr[:-3] #remove trailing 'OR '
+                rangeStr += ') AND '
+            qStr += rangeStr
+
+            filterStr = self.getFilterStr()
+            matchStr = self.getMatch(filterStr) if filterStr else None
+            if matchStr:
+                qStr += matchStr + " AND "
+            qStr = qStr[:-4] #remove trailing ' AND'
+            qStr += ' ORDER '+ self.origQueryStr.split('ORDER')[1]
+
+        q.exec(qStr)
+        q.last()
+        rowsFound = max(0, q.at()+1)
+        if not self.isQueryFilter:
+            q.seek(QSql.BeforeFirstRow)
+        else:
+            #position the db cursor on topRowNo
+            q.seek(QSql.BeforeFirstRow if offsetInRange == 0 else offsetInRange-1)
+        upperBound = min(maxRowsInViewport, rowsFound)
+        self.setRowCount(upperBound)
+        #only visible rows will be filled with data
+        if upperBound > 0:
+            #don't trigger setItem's signals for each cell, instead emit dataChanged for all cells
+            self.blockSignals(True)
+            for x in range(0, upperBound):
+                q.next()
+                for col in range(0, len(self.headerLabels)):
+                    self.setItem(x, col, QStandardItem(q.value(col)))
+            self.blockSignals(False)
+            self.dataChanged.emit(self.createIndex(0,0), self.createIndex(upperBound, len(self.headerLabels)))
+
+    #form a condition string for the query: if filterStr is (partially) present in any of the columns
+    def getMatch (self, filterStr):
+        match = {}
+        for column in self.distinct.keys():
+            match[column] = []
+            for value in self.distinct[column]:
+                if filterStr in value:
+                    match[column].append(value)
+        matchStr = None
+        if any([match[col] for col in match]):
+            matchStr = '( '
+            if match['time']:
+                matchStr += "time IN ('" + "','".join(match['time']) + "') OR"
+            if match['process']:
+                matchStr += "process IN ('" + "','".join(match['process']) + "') OR"
+            if match['dst_host']:
+                matchStr += " (dst_host != '' AND dst_host IN ('" + "','".join(match['dst_host']) + "') ) OR"
+            if match['dst_ip']:
+                matchStr += " (dst_host = '' AND dst_ip IN ('" + "','".join(match['dst_ip']) + "') ) OR"
+            if match['dst_port']:
+                matchStr += " dst_port IN ('" + "','".join(match['dst_port']) + "') OR"
+            if match['rule']:
+                matchStr += " rule IN ('" + "','".join(match['rule']) + "') OR"
+            if match['node']:
+                matchStr += " node IN ('" + "','".join(match['node']) + "') OR"
+            if match['protocol']:
+                matchStr += " protocol IN ('" + "','".join(match['protocol']) + "') OR"
+            matchStr = matchStr[:-2] #remove trailing 'OR'
+            matchStr += ' )'
+        return matchStr
+
+    #extract the filter string if any
+    def getFilterStr(self):
+        filterStr = None
+        if "LIKE '%" in self.origQueryStr:
+            filterStr = self.origQueryStr.split("LIKE '%")[1].split("%")[0]
+        return filterStr
+
+    #extract the action string if any
+    def getActionStr(self):
+        actionStr = None
+        if 'WHERE Action = "' in self.origQueryStr:
+            actionCond = self.origQueryStr.split('WHERE Action = "')[1].split('"')[0]
+            actionStr = "action = '"+actionCond+"'"
+        return actionStr
+
+    def dumpRows(self):
+        rows = []
+        q = QSqlQuery(self.db)
+        q.exec(self.origQueryStr)
+        q.seek(QSql.BeforeFirstRow)
+        while True:
+            q.next()
+            if q.at() == QSql.AfterLastRow:
+                break
+            row = []
+            for col in range(0, len(self.headerLabels)):
+                row.append(q.value(col))
+            rows.append(row)
+        return rows
+
+class ConnectionsTableView(QTableView):
+    # how many rows can potentially be displayed in viewport
+    # the actual number of rows currently displayed may be less than this
+    maxRowsInViewport = 0
+    #vertical scroll bar
+    vScrollBar = None
+
+    def __init__(self, parent):
+        QTableView.__init__(self, parent)
+        #eventFilter to catch key up/down events and wheel events
+        self.installEventFilter(self)
+        self.verticalHeader().setVisible(False)
+        self.horizontalHeader().setDefaultAlignment(QtCore.Qt.AlignCenter)
+        self.horizontalHeader().setStretchLastSection(True)
+        #the built-in vertical scrollBar of this view is always off
+        self.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
+
+    def setVerticalScrollBar(self, vScrollBar):
+        self.vScrollBar = vScrollBar
+        self.vScrollBar.valueChanged.connect(self.onValueChanged)
+        self.vScrollBar.setVisible(False)
+
+    def setModel(self, model):
+        super().setModel(model)
+        model.rowCountChanged.connect(self.onRowCountChanged)
+        model.rowsInserted.connect(self.onRowsInsertedOrRemoved)
+        model.rowsRemoved.connect(self.onRowsInsertedOrRemoved)
+        self.horizontalHeader().sortIndicatorChanged.disconnect()
+        self.setSortingEnabled(False)
+
+    #model().rowCount() is always <= self.maxRowsInViewport
+    #stretch the bottom row; we don't want partial-height rows at the bottom
+    #this will only trigger if rowCount value was changed
+    def onRowsInsertedOrRemoved(self, parent, start, end):
+        if self.model().rowCount() == self.maxRowsInViewport:
+            self.verticalHeader().setStretchLastSection(True)
+        else:
+            self.verticalHeader().setStretchLastSection(False)
+
+    def resizeEvent(self, event):
+        super().resizeEvent(event)
+        #refresh the viewport data based on new geometry
+        self.calculateRowsInViewport()
+        self.model().setRowCount(min(self.maxRowsInViewport, self.model().totalRowCount))
+        self.model().refreshViewport(self.vScrollBar.value(), self.maxRowsInViewport)
+
+    def calculateRowsInViewport(self):
+        rowHeight = self.verticalHeader().defaultSectionSize()
+        #we don't want partial-height rows in viewport, hence .floor()
+        self.maxRowsInViewport = math.floor(self.viewport().height() / rowHeight)
+
+    def onValueChanged(self, vSBNewValue):
+        savedIndex = self.selectionModel().currentIndex()
+        self.model().refreshViewport(vSBNewValue, self.maxRowsInViewport)
+        #restore selection which was removed by model's refreshing the data
+        self.selectionModel().setCurrentIndex(savedIndex, QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent)
+
+    # if ( scrollbar at the top or row limit set):
+    #   let new rows "push down" older rows without changing the scrollbar position
+    # else:
+    #   don't update data in viewport, only change scrollbar position.
+    def onRowCountChanged(self):
+        totalCount = self.model().totalRowCount
+        scrollBar = self.vScrollBar
+        scrollBar.setVisible(True if totalCount > self.maxRowsInViewport else False)
+        scrollBarValue = scrollBar.value()
+        if self.model().limit:
+            newValue = min(scrollBarValue, self.model().limit - self.maxRowsInViewport)
+            scrollBar.setMinimum(0)
+            scrollBar.setMaximum( min(totalCount, self.model().limit) - self.maxRowsInViewport)
+            if scrollBarValue != newValue:
+                #setValue does not trigger valueChanged if new value is the same as old
+                scrollBar.setValue(newValue)
+            else:
+                scrollBar.valueChanged.emit(newValue)
+        else:
+            scrollBar.setMinimum(0)
+            scrollBar.setMaximum(max(0, totalCount - self.maxRowsInViewport))
+            if scrollBarValue == 0:
+                scrollBar.valueChanged.emit(0)
+            elif scrollBarValue > 0:
+                if self.model().prependedRowCount == 0:
+                    scrollBar.valueChanged.emit(scrollBarValue)
+                else:
+                    scrollBar.setValue(scrollBarValue + self.model().prependedRowCount)
+
+    def onKeyUp(self):
+        if self.selectionModel().currentIndex().row() == 0:
+            self.vScrollBar.setValue(self.vScrollBar.value() - 1)
+
+    def onKeyDown(self):
+        if self.selectionModel().currentIndex().row() == self.maxRowsInViewport - 1:
+            self.vScrollBar.setValue(self.vScrollBar.value() + 1)
+
+    def onKeyHome(self):
+        self.vScrollBar.setValue(0)
+        self.selectionModel().setCurrentIndex(self.model().createIndex(0, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent)
+
+    def onKeyEnd(self):
+        self.vScrollBar.setValue(self.vScrollBar.maximum())
+        self.selectionModel().setCurrentIndex(self.model().createIndex(min(self.maxRowsInViewport, self.model().totalRowCount) - 1, 0), QItemSelectionModel.Rows | QItemSelectionModel.SelectCurrent)
+
+    def onKeyPageUp(self):
+        #scroll up only when on the first row
+        if self.selectionModel().currentIndex().row() != 0:
+            return
+        self.vScrollBar.setValue(self.vScrollBar.value() - self.maxRowsInViewport)
+
+    def onKeyPageDown(self):
+        #scroll down only when on the last row
+        if self.selectionModel().currentIndex().row() != self.maxRowsInViewport - 1:
+            return
+        self.vScrollBar.setValue(self.vScrollBar.value() + self.maxRowsInViewport)
+
+    def eventFilter(self, obj, event):
+        if event.type() == QEvent.KeyPress:
+            if event.key() == QtCore.Qt.Key_Up:
+                self.onKeyUp()
+            elif event.key() == QtCore.Qt.Key_Down:
+                self.onKeyDown()
+            elif event.key() == QtCore.Qt.Key_Home:
+                self.onKeyHome()
+            elif event.key() == QtCore.Qt.Key_End:
+                self.onKeyEnd()
+            elif event.key() == QtCore.Qt.Key_PageUp:
+                self.onKeyPageUp()
+            elif event.key() == QtCore.Qt.Key_PageDown:
+                self.onKeyPageDown()
+        elif event.type() == QEvent.Wheel:
+            self.vScrollBar.wheelEvent(event)
+        return False
diff --git a/ui/opensnitch/customwidgets/updownbtndelegate.py b/ui/opensnitch/customwidgets/updownbtndelegate.py
new file mode 100644 (file)
index 0000000..51a9300
--- /dev/null
@@ -0,0 +1,53 @@
+from PyQt5 import Qt, QtCore
+from PyQt5.QtGui import QRegion
+from PyQt5.QtWidgets import QItemDelegate, QAbstractItemView, QPushButton, QWidget, QVBoxLayout, QSizePolicy
+from PyQt5.QtCore import pyqtSignal
+
+class UpDownButtonDelegate(QItemDelegate):
+    clicked = pyqtSignal(int, QtCore.QModelIndex)
+
+    UP=-1
+    DOWN=1
+
+    def paint(self, painter, option, index):
+        if (
+            isinstance(self.parent(), QAbstractItemView)
+            and self.parent().model() is index.model()
+        ):
+            self.parent().openPersistentEditor(index)
+
+    def createEditor(self, parent, option, index):
+        w = QWidget(parent)
+        w.setContentsMargins(0, 0, 0, 0)
+        w.setAutoFillBackground(True)
+
+        layout = QVBoxLayout(w)
+        layout.setContentsMargins(0, 0, 0, 0)
+
+        btnUp = QPushButton(parent)
+        btnUp.setText("⇡")
+        btnUp.setFlat(True)
+        btnUp.clicked.connect(lambda: self._cb_button_clicked(self.UP, index))
+
+        btnDown = QPushButton(parent)
+        btnDown.setText("⇣")
+        btnDown.setFlat(True)
+        btnDown.clicked.connect(lambda: self._cb_button_clicked(self.DOWN, index))
+
+        layout.addWidget(btnUp)
+        layout.addWidget(btnDown)
+        return w
+
+    def _cb_button_clicked(self, action, idx):
+        self.clicked.emit(action, idx)
+
+    def updateEditorGeometry(self, editor, option, index):
+        rect = QtCore.QRect(option.rect)
+        minWidth = editor.minimumSizeHint().width()
+        if rect.width() < minWidth:
+            rect.setWidth(minWidth)
+        editor.setGeometry(rect)
+        # create a new mask based on the option rectangle, then apply it
+        mask = QRegion(0, 0, option.rect.width(), option.rect.height())
+        editor.setProperty('offMask', mask)
+        editor.setMask(mask)
diff --git a/ui/opensnitch/database/__init__.py b/ui/opensnitch/database/__init__.py
new file mode 100644 (file)
index 0000000..a2a3507
--- /dev/null
@@ -0,0 +1,632 @@
+from PyQt5.QtSql import QSqlDatabase, QSqlQueryModel, QSqlQuery
+import threading
+import sys
+import os
+from datetime import datetime, timedelta
+
+class Database:
+    db = None
+    __instance = None
+    DB_IN_MEMORY   = "file::memory:"
+    DB_TYPE_MEMORY = 0
+    DB_TYPE_FILE   = 1
+    DB_JRNL_WAL    = False
+
+    # Sqlite3 journal modes
+    DB_JOURNAL_MODE_LIST = {
+            0: "DELETE",
+            1: "TRUNCATE",
+            2: "PERSIST",
+            3: "MEMORY",
+            4: "WAL",
+            5: "OFF",
+            }
+
+    # increase accordingly whenever the schema is updated
+    DB_VERSION = 3
+
+    @staticmethod
+    def instance():
+        if Database.__instance == None:
+            Database.__instance = Database()
+        return Database.__instance
+
+    def __init__(self, dbname="db"):
+        self._lock = threading.RLock()
+        self.db = None
+        self.db_file = Database.DB_IN_MEMORY
+        self.db_jrnl_wal = Database.DB_JRNL_WAL
+        self.db_name = dbname
+
+    def initialize(self, dbtype=DB_TYPE_MEMORY, dbfile=DB_IN_MEMORY, dbjrnl_wal=DB_JRNL_WAL, db_name="db"):
+        if dbtype != Database.DB_TYPE_MEMORY:
+            self.db_file = dbfile
+            self.db_jrnl_wal = dbjrnl_wal
+        else:
+            # Always disable under pure memory mode
+            self.db_jrnl_wal = False
+
+        is_new_file = not os.path.isfile(self.db_file)
+
+        self.db = QSqlDatabase.addDatabase("QSQLITE", self.db_name)
+        self.db.setDatabaseName(self.db_file)
+        if dbtype == Database.DB_TYPE_MEMORY:
+            self.db.setConnectOptions("QSQLITE_OPEN_URI;QSQLITE_ENABLE_SHARED_CACHE")
+        if not self.db.open():
+            print("\n ** Error opening DB: SQLite driver not loaded. DB name: %s\n" % self.db_file)
+            print("\n    Available drivers: ", QSqlDatabase.drivers())
+            sys.exit(-1)
+
+        db_status, db_error = self.is_db_ok()
+        if db_status is False:
+            print("db.initialize() error:", db_error)
+            return False, db_error
+
+
+        if is_new_file:
+            print("is new file, or IN MEMORY, setting initial schema version")
+            self.set_schema_version(self.DB_VERSION)
+
+        self._create_tables()
+        self._upgrade_db_schema()
+        return True, None
+
+    def close(self):
+        try:
+            if self.db.isOpen():
+                self.db.removeDatabase(self.db_name)
+                self.db.close()
+        except Exception as e:
+            print("db.close() exception:", e)
+
+    def is_db_ok(self):
+        # XXX: quick_check may not be fast enough with some DBs on slow
+        # hardware.
+        q = QSqlQuery("PRAGMA quick_check;", self.db)
+        if q.exec_() is not True:
+            print(q.lastError().driverText())
+            return False, q.lastError().driverText()
+
+        if q.next() and q.value(0) != "ok":
+            return False, "Database is corrupted (1)"
+
+        return True, None
+
+    def get_db(self):
+        return self.db
+
+    def get_db_file(self):
+        return self.db_file
+
+    def get_new_qsql_model(self):
+        return QSqlQueryModel()
+
+    def get_db_name(self):
+        return self.db_name
+
+    def _create_tables(self):
+        # https://www.sqlite.org/wal.html
+        if self.db_file == Database.DB_IN_MEMORY:
+            self.set_schema_version(self.DB_VERSION)
+            # Disable journal (default)
+            self.set_journal_mode(5)
+        elif self.db_jrnl_wal is True:
+            # Set WAL mode (file+memory)
+            self.set_journal_mode(4)
+        else:
+            # Set DELETE mode (file)
+            self.set_journal_mode(0)
+        q = QSqlQuery("create table if not exists connections (" \
+                "time text, " \
+                "node text, " \
+                "action text, " \
+                "protocol text, " \
+                "src_ip text, " \
+                "src_port text, " \
+                "dst_ip text, " \
+                "dst_host text, " \
+                "dst_port text, " \
+                "uid text, " \
+                "pid text, " \
+                "process text, " \
+                "process_args text, " \
+                "process_cwd text, " \
+                "rule text, " \
+                "UNIQUE(node, action, protocol, src_ip, src_port, dst_ip, dst_port, uid, pid, process, process_args))",
+                self.db)
+        q = QSqlQuery("create index time_index on connections (time)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index action_index on connections (action)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index protocol_index on connections (protocol)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index dst_host_index on connections (dst_host)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index process_index on connections (process)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index dst_ip_index on connections (dst_ip)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index dst_port_index on connections (dst_port)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index rule_index on connections (rule)", self.db)
+        q.exec_()
+        q = QSqlQuery("create index node_index on connections (node)", self.db)
+        q.exec_()
+        q = QSqlQuery("CREATE INDEX details_query_index on connections (process, process_args, uid, pid, dst_ip, dst_host, dst_port, action, node, protocol)", self.db)
+        q.exec_()
+
+        q = QSqlQuery("create table if not exists nodes (" \
+                "addr text primary key," \
+                "hostname text," \
+                "daemon_version text," \
+                "daemon_uptime text," \
+                "daemon_rules text," \
+                "cons text," \
+                "cons_dropped text," \
+                "version text," \
+                "status text, " \
+                "last_connection text)"
+                , self.db)
+        q.exec_()
+
+        q = QSqlQuery("create table if not exists rules (" \
+                "time text, " \
+                "node text, " \
+                "name text, " \
+                "enabled text, " \
+                "precedence text, " \
+                "action text, " \
+                "duration text, " \
+                "operator_type text, " \
+                "operator_sensitive text, " \
+                "operator_operand text, " \
+                "operator_data text, " \
+                "description text, " \
+                "nolog text, " \
+                "created text, " \
+                "UNIQUE(node, name)"
+                ")", self.db)
+        q.exec_()
+        q = QSqlQuery("create index rules_index on rules (time)", self.db)
+        q.exec_()
+
+        q = QSqlQuery("create table if not exists hosts (what text primary key, hits integer)", self.db)
+        q.exec_()
+        q = QSqlQuery("create table if not exists procs (what text primary key, hits integer)", self.db)
+        q.exec_()
+        q = QSqlQuery("create table if not exists addrs (what text primary key, hits integer)", self.db)
+        q.exec_()
+        q = QSqlQuery("create table if not exists ports (what text primary key, hits integer)", self.db)
+        q.exec_()
+        q = QSqlQuery("create table if not exists users (what text primary key, hits integer)", self.db)
+        q.exec_()
+
+    def get_schema_version(self):
+        q = QSqlQuery("PRAGMA user_version;", self.db)
+        q.exec_()
+        if q.next():
+            print("schema version:", q.value(0))
+            return int(q.value(0))
+
+        return 0
+
+    def set_schema_version(self, version):
+        print("setting schema version to:", version)
+        q = QSqlQuery("PRAGMA user_version = {0}".format(version), self.db)
+        if q.exec_() == False:
+            print("Error updating updating schema version:", q.lastError().text())
+
+    def get_journal_mode(self):
+        q = QSqlQuery("PRAGMA journal_mode;", self.db)
+        q.exec_()
+        if q.next():
+            return str(q.value(0))
+
+        return str("unknown")
+
+    def set_journal_mode(self, mode):
+        # https://www.sqlite.org/wal.html
+        mode_str = Database.DB_JOURNAL_MODE_LIST[mode]
+        if self.get_journal_mode().lower() != mode_str.lower():
+            print("Setting journal_mode: ", mode_str)
+            q = QSqlQuery("PRAGMA journal_mode = {modestr};".format(modestr = mode_str), self.db)
+            if q.exec_() == False:
+                print("Error updating PRAGMA journal_mode:", q.lastError().text())
+                return False
+        if mode == 3 or mode == 5:
+            print("Setting DB memory optimizations")
+            q = QSqlQuery("PRAGMA synchronous = OFF;", self.db)
+            if q.exec_() == False:
+                print("Error updating PRAGMA synchronous:", q.lastError().text())
+                return False
+            q = QSqlQuery("PRAGMA cache_size=10000;", self.db)
+            if q.exec_() == False:
+                print("Error updating PRAGMA cache_size:", q.lastError().text())
+                return False
+        else:
+            print("Setting synchronous = NORMAL")
+            q = QSqlQuery("PRAGMA synchronous = NORMAL;", self.db)
+            if q.exec_() == False:
+                print("Error updating PRAGMA synchronous:", q.lastError().text())
+
+        return True
+
+    def _upgrade_db_schema(self):
+        migrations_path = os.path.dirname(os.path.realpath(__file__)) + "/migrations"
+        schema_version = self.get_schema_version()
+        if schema_version == self.DB_VERSION:
+            print("db schema is up to date")
+            return
+        while schema_version < self.DB_VERSION:
+            schema_version += 1
+            try:
+                print("applying schema upgrade:", schema_version)
+                self._apply_db_upgrade("{0}/upgrade_{1}.sql".format(migrations_path, schema_version))
+            except Exception as e:
+                print("Not applying upgrade_{0}.sql:".format(schema_version), e)
+                return
+        self.set_schema_version(schema_version)
+
+    def _apply_db_upgrade(self, file):
+        print("applying upgrade from:", file)
+        q = QSqlQuery(self.db)
+        with open(file) as f:
+            for line in f.readlines():
+                # skip comments
+                if line.startswith("--"):
+                    continue
+                print("applying upgrade:", line, end="")
+                if q.exec(line) == False:
+                    print("\tError:", q.lastError().text())
+                else:
+                    print("\tOK")
+
+    def optimize(self):
+        """https://www.sqlite.org/pragma.html#pragma_optimize
+        """
+        q = QSqlQuery("PRAGMA optimize;", self.db)
+        q.exec_()
+
+    def clean(self, table):
+        with self._lock:
+            q = QSqlQuery("delete from " + table, self.db)
+            q.exec_()
+
+    def vacuum(self):
+        q = QSqlQuery("VACUUM;", self.db)
+        q.exec_()
+
+    def clone_db(self, name):
+        return QSqlDatabase.cloneDatabase(self.db, name)
+
+    def clone(self):
+        q = QSqlQuery(".dump", self.db)
+        q.exec_()
+
+    def transaction(self):
+        self.db.transaction()
+
+    def commit(self):
+        self.db.commit()
+
+    def rollback(self):
+        self.db.rollback()
+
+    def get_total_records(self):
+        try:
+            q = QSqlQuery("SELECT count(*) FROM connections", self.db)
+            if q.exec_() and q.first():
+                r = q.value(0)
+        except Exception as e:
+            print("db, get_total_records() error:", e)
+
+    def get_newest_record(self):
+        try:
+            q = QSqlQuery("SELECT time FROM connections ORDER BY 1 DESC LIMIT 1", self.db)
+            if q.exec_() and q.first():
+                return q.value(0)
+        except Exception as e:
+            print("db, get_newest_record() error:", e)
+        return 0
+
+    def get_oldest_record(self):
+        try:
+            q = QSqlQuery("SELECT time FROM connections ORDER BY 1 ASC LIMIT 1", self.db)
+            if q.exec_() and q.first():
+                return q.value(0)
+        except Exception as e:
+            print("db, get_oldest_record() error:", e)
+        return 0
+
+    def purge_oldest(self, max_days_to_keep):
+        try:
+            oldt = self.get_oldest_record()
+            newt = self.get_newest_record()
+            if oldt == None or newt == None or oldt == 0 or newt == 0:
+                return -1
+
+            oldest = datetime.strptime(oldt, "%Y-%m-%d %H:%M:%S.%f")
+            newest = datetime.strptime(newt, "%Y-%m-%d %H:%M:%S.%f")
+            diff = newest - oldest
+            date_to_purge = datetime.now() - timedelta(days=max_days_to_keep)
+
+            if diff.days >= max_days_to_keep:
+                q = QSqlQuery(self.db)
+                q.prepare("DELETE FROM connections WHERE time < ?")
+                q.bindValue(0, str(date_to_purge))
+                if q.exec_():
+                    print("purge_oldest() {0} records deleted".format(q.numRowsAffected()))
+                    return q.numRowsAffected()
+        except Exception as e:
+            print("db, purge_oldest() error:", e)
+
+        return -1
+
+    def select(self, qstr):
+        try:
+            return QSqlQuery(qstr, self.db)
+        except Exception as e:
+            print("db, select() exception: ", e)
+
+        return None
+
+    def remove(self, qstr):
+        try:
+            q = QSqlQuery(qstr, self.db)
+            if q.exec_():
+                return True
+            else:
+                print("db, remove() ERROR: ", qstr)
+                print(q.lastError().driverText())
+        except Exception as e:
+            print("db, remove exception: ", e)
+
+        return False
+
+    def _insert(self, query_str, columns):
+        with self._lock:
+            try:
+
+                q = QSqlQuery(self.db)
+                q.prepare(query_str)
+                for idx, v in enumerate(columns):
+                    q.bindValue(idx, v)
+                if q.exec_():
+                    return True
+                else:
+                    print("_insert() ERROR", query_str)
+                    print(q.lastError().driverText())
+
+            except Exception as e:
+                print("_insert exception", e)
+            finally:
+                q.finish()
+
+        return False
+
+    def insert(self, table, fields, columns, update_field=None, update_values=None, action_on_conflict="REPLACE"):
+        if update_field != None:
+            action_on_conflict = ""
+        else:
+            action_on_conflict = "OR " + action_on_conflict
+
+        qstr = "INSERT " + action_on_conflict + " INTO " + table + " " + fields + " VALUES("
+        update_fields=""
+        for col in columns:
+            qstr += "?,"
+        qstr = qstr[0:len(qstr)-1] + ")"
+
+        if update_field != None:
+            # NOTE: UPSERTS on sqlite are only supported from v3.24 on.
+            # On Ubuntu16.04/18 for example (v3.11/3.22) updating a record on conflict
+            # fails with "Parameter count error"
+            qstr += " ON CONFLICT (" + update_field + ") DO UPDATE SET "
+            for idx, field in enumerate(update_values):
+                qstr += str(field) + "=excluded." + str(field) + ","
+
+            qstr = qstr[0:len(qstr)-1]
+
+        return self._insert(qstr, columns)
+
+    def update(self, table, fields, values, condition=None, action_on_conflict="OR IGNORE"):
+        qstr = "UPDATE " + action_on_conflict + " " + table + " SET " + fields
+        if condition != None:
+            qstr += " WHERE " + condition
+        try:
+            with self._lock:
+                q = QSqlQuery(qstr, self.db)
+                q.prepare(qstr)
+                for idx, v in enumerate(values):
+                    q.bindValue(idx, v)
+                if not q.exec_():
+                    print("update ERROR:", qstr, "values:", values)
+                    print(q.lastError().driverText())
+
+        except Exception as e:
+            print("update() exception:", e)
+        finally:
+            q.finish()
+
+    def _insert_batch(self, query_str, fields, values):
+        result=True
+        with self._lock:
+            try:
+                q = QSqlQuery(self.db)
+                q.prepare(query_str)
+                q.addBindValue(fields)
+                q.addBindValue(values)
+                if not q.execBatch():
+                    print("_insert_batch() db error:", query_str)
+                    print(q.lastError().driverText())
+                    print("\t", fields)
+                    print("\t", values)
+
+                    result=False
+            except Exception as e:
+                print("_insert_batch() exception:", e)
+            finally:
+                q.finish()
+
+        return result
+
+    def insert_batch(self, table, db_fields, db_columns, fields, values, update_field=None, update_value=None, action_on_conflict="REPLACE"):
+        action = "OR " + action_on_conflict
+        if update_field != None:
+            action = ""
+
+        qstr = "INSERT " + action + " INTO " + table + " (" + db_fields[0] + "," + db_fields[1] + ") VALUES("
+        for idx in db_columns:
+            qstr += "?,"
+        qstr = qstr[0:len(qstr)-1] + ")"
+
+        if self._insert_batch(qstr, fields, values) == False:
+            self.update_batch(table, db_fields, db_columns, fields, values, update_field, update_value, action_on_conflict)
+
+    def update_batch(self, table, db_fields, db_columns, fields, values, update_field=None, update_value=None, action_on_conflict="REPLACE"):
+        for idx, i in enumerate(values):
+            s = "UPDATE " + table + " SET " + "%s=(select hits from %s)+%s" % (db_fields[1], table, values[idx])
+            s += "  WHERE %s=\"%s\"," % (db_fields[0], fields[idx])
+            s = s[0:len(s)-1]
+            with self._lock:
+                q = QSqlQuery(s, self.db)
+                if not q.exec_():
+                    print("update batch ERROR", s)
+                    print(q.lastError().driverText())
+
+    def dump(self):
+        q = QSqlQuery(".dump", db=self.db)
+        q.exec_()
+
+    def get_query(self, table, fields):
+        return "SELECT " + fields + " FROM " + table
+
+    def empty_rule(self, name=""):
+        if name == "":
+            return
+        qstr = "DELETE FROM connections WHERE rule = ?"
+
+        with self._lock:
+            q = QSqlQuery(qstr, self.db)
+            q.prepare(qstr)
+            q.addBindValue(name)
+            if not q.exec_():
+                print("db, empty_rule() ERROR: ", qstr)
+                print(q.lastError().driverText())
+
+    def delete_rule(self, name, node_addr):
+        qstr = "DELETE FROM rules WHERE name=?"
+        if node_addr != None:
+            qstr = qstr + " AND node=?"
+
+        with self._lock:
+            q = QSqlQuery(qstr, self.db)
+            q.prepare(qstr)
+            q.addBindValue(name)
+            if node_addr != None:
+                q.addBindValue(node_addr)
+            if not q.exec_():
+                print("db, delete_rule() ERROR: ", qstr)
+                print(q.lastError().driverText())
+                return False
+
+        return True
+
+    def delete_rules_by_field(self, field, values):
+        if len(values) == 0:
+            return True
+
+        qstr = "DELETE FROM rules WHERE "
+        for v in values:
+            qstr += field + "=? OR "
+
+        qstr = qstr[:-4]
+
+        with self._lock:
+            q = QSqlQuery(qstr, self.db)
+            q.prepare(qstr)
+
+            for v in values:
+                q.addBindValue(v)
+
+            if not q.exec_():
+                print("db, delete_rule_by_field() ERROR: ", qstr)
+                print(q.lastError().driverText())
+                return False
+
+        return True
+
+    def get_connection_by_field(self, field, date):
+        """
+        """
+        qstr = "SELECT * FROM connections WHERE {0}=?".format(field)
+
+        q = QSqlQuery(qstr, self.db)
+        q.prepare(qstr)
+        q.addBindValue(date)
+        q.exec_()
+
+        return q
+
+    def get_rule(self, rule_name, node_addr=None):
+        """
+        get rule records, given the name of the rule and the node
+        """
+        qstr = "SELECT * FROM rules WHERE name=?"
+        if node_addr != None:
+            qstr = qstr + " AND node=?"
+
+        q = QSqlQuery(qstr, self.db)
+        q.prepare(qstr)
+        q.addBindValue(rule_name)
+        if node_addr != None:
+            q.addBindValue(node_addr)
+        q.exec_()
+
+        return q
+
+    def get_rules(self, node_addr):
+        """
+        get rule records, given the name of the rule and the node
+        """
+        qstr = "SELECT * FROM rules WHERE node=?"
+        q = QSqlQuery(qstr, self.db)
+        q.prepare(qstr)
+        q.addBindValue(node_addr)
+        if not q.exec_():
+            return None
+
+        return q
+
+    def insert_rule(self, rule, node_addr):
+        self.insert("rules",
+            "(time, node, name, description, enabled, precedence, nolog, action, duration, operator_type, operator_sensitive, operator_operand, operator_data)",
+                (datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+                    node_addr, rule.name, rule.description,
+                    str(rule.enabled), str(rule.precedence), str(rule.nolog),
+                    rule.action, rule.duration, rule.operator.type,
+                    str(rule.operator.sensitive), rule.operator.operand, rule.operator.data),
+                action_on_conflict="IGNORE")
+
+    def rule_exists(self, rule, node_addr):
+        qstr = "SELECT node, name, action, duration, operator_type, operator_operand, operator_data " \
+            " FROM rules WHERE " \
+            "name=? AND " \
+            "node=? AND " \
+            "action=? AND " \
+            "duration=? AND " \
+            "operator_type=? AND " \
+            "operator_operand=? AND " \
+            "operator_data=?"
+        q = QSqlQuery(qstr, self.db)
+        q.prepare(qstr)
+        q.addBindValue(rule.name)
+        q.addBindValue(node_addr)
+        q.addBindValue(rule.action)
+        q.addBindValue(rule.duration)
+        q.addBindValue(rule.operator.type)
+        q.addBindValue(rule.operator.operand)
+        q.addBindValue(rule.operator.data)
+        if not q.exec_() or q.next() == False:
+            return None
+
+        return q
diff --git a/ui/opensnitch/database/enums.py b/ui/opensnitch/database/enums.py
new file mode 100644 (file)
index 0000000..72e98dc
--- /dev/null
@@ -0,0 +1,34 @@
+
+class ConnFields():
+    Time = 0
+    Node = 1
+    Action = 2
+    Protocol = 3
+    SrcIP = 4
+    SrcPort = 5
+    DstIP = 6
+    DstHost = 7
+    DstPort = 8
+    UID = 9
+    PID = 10
+    Process = 11
+    Cmdline = 12
+    CWD = 13
+    Rule = 14
+
+class RuleFields():
+    """These fields must be in the order defined in the DB"""
+    Time = 0
+    Node = 1
+    Name = 2
+    Enabled = 3
+    Precedence = 4
+    Action = 5
+    Duration = 6
+    OpType = 7
+    OpSensitive = 8
+    OpOperand = 9
+    OpData = 10
+    Description = 11
+    NoLog = 12
+    Created = 13
diff --git a/ui/opensnitch/database/migrations/upgrade_1.sql b/ui/opensnitch/database/migrations/upgrade_1.sql
new file mode 100644 (file)
index 0000000..8a35251
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE rules ADD COLUMN description;
diff --git a/ui/opensnitch/database/migrations/upgrade_2.sql b/ui/opensnitch/database/migrations/upgrade_2.sql
new file mode 100644 (file)
index 0000000..73a5ad5
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE rules ADD COLUMN nolog;
diff --git a/ui/opensnitch/database/migrations/upgrade_3.sql b/ui/opensnitch/database/migrations/upgrade_3.sql
new file mode 100644 (file)
index 0000000..60aa2dc
--- /dev/null
@@ -0,0 +1 @@
+ALTER TABLE rules ADD COLUMN created;
diff --git a/ui/opensnitch/desktop_parser.py b/ui/opensnitch/desktop_parser.py
new file mode 100644 (file)
index 0000000..fbbc028
--- /dev/null
@@ -0,0 +1,194 @@
+from threading import Lock
+import configparser
+import threading
+import glob
+import os
+import re
+import shutil
+import locale
+
+is_pyinotify_available = True
+try:
+    import pyinotify
+except Exception as e:
+    is_pyinotify_available = False
+    print("Error importing pyinotify:", e)
+
+DESKTOP_PATHS = tuple([
+    os.path.join(d, 'applications')
+    for d in os.getenv('XDG_DATA_DIRS', '/usr/share/').split(':')
+])
+
+class LinuxDesktopParser(threading.Thread):
+    def __init__(self):
+        threading.Thread.__init__(self)
+        self.lock = Lock()
+        self.daemon = True
+        self.running = False
+        self.apps = {}
+        self.apps_by_name = {}
+        self.get_locale()
+        # some things are just weird
+        # (not really, i don't want to keep track of parent pids
+        # just because of icons though, this hack is way easier)
+        self.fixes = {
+            '/opt/google/chrome/chrome': '/opt/google/chrome/google-chrome',
+            '/usr/lib/firefox/firefox': '/usr/lib/firefox/firefox.sh',
+            '/usr/bin/pidgin.orig': '/usr/bin/pidgin'
+        }
+
+        for desktop_path in DESKTOP_PATHS:
+            if not os.path.exists(desktop_path):
+                continue
+            for desktop_file in glob.glob(os.path.join(desktop_path, '*.desktop')):
+                self._parse_desktop_file(desktop_file)
+
+        if is_pyinotify_available:
+            self.start()
+
+    def get_locale(self):
+        try:
+            self.locale = locale.getlocale()[0]
+            self.locale_country = self.locale.split("_")[0]
+        except Exception:
+            self.locale = ""
+            self.locale_country = ""
+
+    def _parse_exec(self, cmd):
+        try:
+            is_flatpak = re.search(r'^/usr/[s]*bin/flatpak.*--command=([a-zA-Z0-9-_\/\.\+]+)', cmd)
+            if is_flatpak:
+                return is_flatpak.group(1)
+
+            # remove stuff like %U
+            cmd = re.sub( r'%[a-zA-Z]+', '', cmd)
+            # remove 'env .... command'
+            cmd = re.sub( r'^env\s+[^\s]+\s', '', cmd)
+            # split && trim
+            cmd = cmd.split(' ')[0].strip()
+            # remove quotes
+            cmd = re.sub( r'["\']+', '', cmd)
+
+            # check if we need to resolve the path
+            if len(cmd) > 0 and cmd[0] != '/':
+                for path in os.environ["PATH"].split(os.pathsep):
+                    filename = os.path.join(path, cmd)
+                    if os.path.exists(filename):
+                        cmd = filename
+                        break
+
+        except Exception as e:
+            print("desktop_parser._parse_exec() exception:", e)
+
+        return cmd
+
+    def get_app_description(self, parser):
+        try:
+            desc = parser.get('Desktop Entry', 'Comment[%s]' % self.locale_country, raw=True, fallback=None)
+            if desc == None:
+                desc = parser.get('Desktop Entry', 'Comment[%s]' % self.locale, raw=True, fallback=None)
+
+            if desc == None:
+                desc = parser.get('Desktop Entry', 'Comment', raw=True, fallback=None)
+
+            return desc
+        except:
+            return None
+
+    def discover_app_icon(self, app_name):
+        # more hacks
+        # normally qt will find icons if the system if configured properly.
+        # if it's not, qt won't be able to find the icon by using QIcon().fromTheme(""),
+        # so we fallback to try to determine if the icon exist in some well known system paths.
+        icon_dirs = ("/usr/share/icons/gnome/48x48/apps/", "/usr/share/pixmaps/", "/usr/share/icons/hicolor/48x48/apps/", "/usr/share/icons/HighContrast/48x48/apps/")
+        icon_exts = (".png", ".xpm", ".svg")
+        for idir in icon_dirs:
+            for iext in icon_exts:
+                if iext in app_name:
+                    iconPath = idir + app_name
+                    if os.path.exists(iconPath):
+                        return iconPath
+                else:
+                    iconPath = idir + app_name + iext
+                    if os.path.exists(iconPath):
+                        return iconPath
+
+    def _parse_desktop_file(self, desktop_path):
+        parser = configparser.ConfigParser(strict=False)  # Allow duplicate config entries
+        try:
+            basename = os.path.basename(desktop_path)[:-8]
+            parser.read(desktop_path, 'utf8')
+
+            cmd = parser.get('Desktop Entry', 'exec', raw=True, fallback=None)
+            if cmd == None:
+                cmd = parser.get('Desktop Entry', 'Exec', raw=True, fallback=None)
+            if cmd is not None:
+                cmd  = self._parse_exec(cmd)
+                icon = parser.get('Desktop Entry', 'Icon', raw=True, fallback=None)
+                name = parser.get('Desktop Entry', 'Name', raw=True, fallback=None)
+                desc = self.get_app_description(parser)
+
+                if icon == None:
+                    # Some .desktop files doesn't have the Icon entry
+                    # FIXME: even if we return an icon, if the DE is not properly configured,
+                    # it won't be loaded/displayed.
+                    icon = self.discover_app_icon(basename)
+
+                with self.lock:
+                    # The Exec entry may have an absolute path to a binary or just the binary with parameters.
+                    # /path/binary or binary, so save both
+                    self.apps[cmd] = (name, icon, desc, desktop_path)
+                    self.apps[basename] = (name, icon, desc, desktop_path)
+                    # if the command is a symlink, add the real binary too
+                    if os.path.islink(cmd):
+                        link_to = os.path.realpath(cmd)
+                        self.apps[link_to] = (name, icon, desc, desktop_path)
+        except:
+            print("Exception parsing .desktop file ", desktop_path)
+
+    def get_info_by_path(self, path, default_icon):
+        def_name = os.path.basename(path)
+        # apply fixes
+        for orig, to in self.fixes.items():
+            if path == orig:
+                path = to
+                break
+
+        app_name = self.apps.get(path)
+        if app_name == None:
+            # last try to get a default terminal icon
+            for def_icon in ("utilities-terminal", "gnome-terminal", "xfce-terminal"):
+                test = self.apps.get(def_name, (def_name, def_icon, "", None))
+                if test != None:
+                    return test
+
+            return self.apps.get(def_name, (def_name, default_icon, "", None))
+
+        return self.apps.get(path, (def_name, default_icon, "", None))
+
+    def get_info_by_binname(self, name, default_icon):
+        def_name = os.path.basename(name)
+        return self.apps.get(def_name, (def_name, default_icon, None))
+
+    def run(self):
+        self.running = True
+        wm = pyinotify.WatchManager()
+        notifier = pyinotify.Notifier(wm)
+
+        def inotify_callback(event):
+            if event.mask == pyinotify.IN_CLOSE_WRITE:
+                self._parse_desktop_file(event.pathname)
+
+            elif event.mask == pyinotify.IN_DELETE:
+                with self.lock:
+                    for cmd, data in self.apps.items():
+                        if data[2] == event.pathname:
+                            del self.apps[cmd]
+                            break
+
+        for p in DESKTOP_PATHS:
+            if os.path.exists(p):
+                wm.add_watch(p,
+                             pyinotify.IN_CLOSE_WRITE | pyinotify.IN_DELETE,
+                             inotify_callback)
+        notifier.loop()
diff --git a/ui/opensnitch/dialogs/__init__.py b/ui/opensnitch/dialogs/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ui/opensnitch/dialogs/conndetails.py b/ui/opensnitch/dialogs/conndetails.py
new file mode 100644 (file)
index 0000000..98dbe92
--- /dev/null
@@ -0,0 +1,60 @@
+
+from opensnitch.nodes import Nodes
+from opensnitch.database import Database
+from opensnitch.database.enums import ConnFields
+from opensnitch.utils import Utils
+from opensnitch.utils.infowindow import InfoWindow
+from PyQt5.QtCore import QCoreApplication as QC
+
+class ConnDetails(InfoWindow):
+    """Display a small dialog with the details of a connection
+    """
+
+    def __init__(self, parent):
+        super().__init__(parent)
+
+        self._db = Database.instance()
+        self._nodes = Nodes.instance()
+
+    def showByField(self, field, value):
+        records = self._db.get_connection_by_field(field, value)
+        if not records.next():
+            return
+
+        node = records.value(ConnFields.Node)
+        uid = records.value(ConnFields.UID)
+        if self._nodes.is_local(node):
+            uid = Utils.get_user_id(uid)
+
+        conn_text = QC.translate("stats", """
+<b>{0}</b><br><br>
+<b>Time:</b> {1}<br><br>
+<b>Process:</b><br>{2}<br>
+<b>Cmdline:</b><br>{3}<br>
+<b>CWD:</b><br>{4}<br><br>
+<b>UID:</b> {5} <b>PID:</b> {6}<br>
+<br>
+<b>Node:</b> {7}<br><br>
+<b>{8}</b> {9}:{10} -> {11} ({12}):{13}
+<br><br>
+<b>Rule:</b><br>
+{14}
+""".format(
+                    records.value(ConnFields.Action).upper(),
+                    records.value(ConnFields.Time),
+                    records.value(ConnFields.Process),
+                    records.value(ConnFields.Cmdline),
+                    records.value(ConnFields.CWD),
+                    uid,
+                    records.value(ConnFields.PID),
+                    node,
+                    records.value(ConnFields.Protocol).upper(),
+                    records.value(ConnFields.SrcPort),
+                    records.value(ConnFields.SrcIP),
+                    records.value(ConnFields.DstIP),
+                    records.value(ConnFields.DstHost),
+                    records.value(ConnFields.DstPort),
+                    records.value(ConnFields.Rule)
+                ))
+
+        self.showText(conn_text)
diff --git a/ui/opensnitch/dialogs/firewall.py b/ui/opensnitch/dialogs/firewall.py
new file mode 100644 (file)
index 0000000..8b75493
--- /dev/null
@@ -0,0 +1,398 @@
+import sys
+import time
+import os
+import os.path
+import json
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QCoreApplication as QC
+
+from opensnitch.utils import Icons, Message
+from opensnitch.config import Config
+from opensnitch.nodes import Nodes
+from opensnitch.dialogs.firewall_rule import FwRuleDialog
+import opensnitch.firewall as Fw
+import opensnitch.firewall.profiles as FwProfiles
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+DIALOG_UI_PATH = "%s/../res/firewall.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class FirewallDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+    LOG_TAG = "[fw dialog]"
+
+    COMBO_IN = 0
+    COMBO_OUT = 1
+
+    POLICY_ACCEPT = 0
+    POLICY_DROP = 1
+
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+
+    def __init__(self, parent=None, appicon=None, node=None):
+        QtWidgets.QDialog.__init__(self, parent)
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+        self.appicon = appicon
+
+        # TODO: profiles are ready to be used. They need to be tested, and
+        # create some default profiles (home, office, public, ...)
+        self.comboProfile.setVisible(False)
+        self.lblProfile.setVisible(False)
+
+        self.secHighIcon = Icons.new(self, "security-high")
+        self.secMediumIcon = Icons.new(self, "security-medium")
+        self.secLowIcon = Icons.new(self, "security-low")
+        self.lblStatusIcon.setPixmap(self.secHighIcon.pixmap(96, 96))
+
+        self._fwrule_dialog = FwRuleDialog(appicon=self.appicon)
+        self._cfg = Config.get()
+        self._fw = Fw.Firewall.instance()
+        self._nodes = Nodes.instance()
+        self._fw_profiles = {}
+        self._last_profile = {
+            self.COMBO_IN: FwProfiles.ProfileAcceptInput.value,
+            self.COMBO_OUT: FwProfiles.ProfileAcceptInput.value
+        }
+
+        self._notification_callback.connect(self._cb_notification_callback)
+        self._notifications_sent = {}
+
+        self._nodes.nodesUpdated.connect(self._cb_nodes_updated)
+        self.cmdNewRule.clicked.connect(self._cb_new_rule_clicked)
+        self.cmdAllowOUTService.clicked.connect(self._cb_allow_out_service_clicked)
+        self.cmdAllowINService.clicked.connect(self._cb_allow_in_service_clicked)
+        self.comboInput.currentIndexChanged.connect(lambda: self._cb_combo_policy_changed(self.COMBO_IN))
+        self.comboProfile.currentIndexChanged.connect(self._cb_combo_profile_changed)
+        self.sliderFwEnable.valueChanged.connect(self._cb_enable_fw_changed)
+        self.cmdClose.clicked.connect(self._cb_close_clicked)
+        self.cmdHelp.clicked.connect(
+            lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_SYSFW_URL))
+        )
+
+        # TODO: when output policy is set to Drop, all outbound traffic is
+        # blocked.
+        #self.comboOutput.currentIndexChanged.connect(lambda: self._cb_combo_policy_changed(self.COMBO_OUT))
+
+        if QtGui.QIcon.hasThemeIcon("document-new"):
+            return
+
+        closeIcon = Icons.new(self, "window-close")
+        excludeIcon = Icons.new(self, "go-up")
+        allowInIcon = Icons.new(self, "go-down")
+        newIcon = Icons.new(self, "document-new")
+        helpIcon = Icons.new(self, "help-browser")
+        self.cmdClose.setIcon(closeIcon)
+        self.cmdAllowOUTService.setIcon(excludeIcon)
+        self.cmdAllowINService.setIcon(allowInIcon)
+        self.cmdNewRule.setIcon(newIcon)
+        self.cmdHelp.setIcon(helpIcon)
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def _cb_notification_callback(self, reply):
+        self.comboInput.setEnabled(True)
+        if reply.id in self._notifications_sent:
+            if reply.code == ui_pb2.OK:
+                rep = self._notifications_sent[reply.id]
+                self._set_status_successful(QC.translate("firewall", "Configuration applied."))
+
+            else:
+                self._set_status_error(QC.translate("firewall", "Error: {0}").format(reply.data))
+
+            del self._notifications_sent[reply.id]
+        else:
+            print(self.LOG_TAG, "unknown notification:", reply)
+
+
+    @QtCore.pyqtSlot(int)
+    def _cb_nodes_updated(self, total):
+        self._check_fw_status()
+
+    def _cb_combo_profile_changed(self, idx):
+        combo_profile = self._fw_profiles[idx]
+        json_profile = json.dumps(list(combo_profile.values())[0]['Profile'])
+
+        for addr in self._nodes.get():
+            fwcfg = self._nodes.get_node(addr)['firewall']
+            ok, err = self._fw.apply_profile(addr, json_profile)
+            if ok:
+                self.send_notification(addr, fwcfg)
+            else:
+                self._set_status_error(QC.translate("firewall", "error adding profile extra rules:", err))
+
+    def _cb_combo_policy_changed(self, combo):
+        self._reset_status_message()
+        self.comboInput.setEnabled(False)
+
+        wantedProfile = FwProfiles.ProfileAcceptInput.value
+        if combo == self.COMBO_OUT:
+            wantedProfile = FwProfiles.ProfileAcceptOutput.value
+            if self.comboOutput.currentIndex() == self.POLICY_DROP:
+                wantedProfile = FwProfiles.ProfileDropOutput.value
+        else:
+            if self.comboInput.currentIndex() == self.POLICY_DROP:
+                wantedProfile = FwProfiles.ProfileDropInput.value
+
+
+        if combo == self.COMBO_IN and \
+                self.comboInput.currentIndex() == self.POLICY_ACCEPT:
+            json_profile = json.dumps(FwProfiles.ProfileDropInput.value)
+            for addr in self._nodes.get():
+                fwcfg = self._nodes.get_node(addr)['firewall']
+                ok, err = self._fw.delete_profile(addr, json_profile)
+                if not ok:
+                    print(err)
+
+
+        json_profile = json.dumps(wantedProfile)
+        for addr in self._nodes.get():
+            fwcfg = self._nodes.get_node(addr)['firewall']
+            ok, err = self._fw.apply_profile(addr, json_profile)
+            if ok:
+                self.send_notification(addr, fwcfg)
+            else:
+                self._set_status_error(QC.translate("firewall", "Policy not applied: {0}".format(err)))
+
+        self._last_profile[combo] = wantedProfile
+
+    def _cb_new_rule_clicked(self):
+        self.new_rule()
+
+    def _cb_allow_out_service_clicked(self):
+        self.allow_out_service()
+
+    def _cb_allow_in_service_clicked(self):
+        self.allow_in_service()
+
+    def _cb_enable_fw_changed(self, enable):
+        if self._nodes.count() == 0:
+            self.sliderFwEnable.blockSignals(True)
+            self.sliderFwEnable.setValue(False)
+            self.sliderFwEnable.blockSignals(False)
+            return
+        self.enable_fw(enable)
+
+    def _cb_close_clicked(self):
+        self._close()
+
+    def _load_nodes(self):
+        self._nodes = self._nodes.get()
+
+    def _close(self):
+        self.hide()
+
+    def _change_fw_backend(self, addr, node_cfg):
+        nid, notif = self._nodes.change_node_config(addr, node_cfg, self._notification_callback)
+        self._notifications_sent[nid] = notif
+
+    def showEvent(self, event):
+        super(FirewallDialog, self).showEvent(event)
+        self._reset_fields()
+        self._check_fw_status()
+        self._fw_profiles = FwProfiles.Profiles.load_predefined_profiles()
+        self.comboProfile.blockSignals(True)
+        for pr in self._fw_profiles:
+            self.comboProfile.addItem([pr[k] for k in pr][0]['Name'])
+        self.comboProfile.blockSignals(False)
+
+    def send_notification(self, node_addr, fw_config):
+        self._set_status_message(QC.translate("firewall", "Applying changes..."))
+        nid, notif = self._nodes.reload_fw(node_addr, fw_config, self._notification_callback)
+        self._notifications_sent[nid] = {'addr': node_addr, 'notif': notif}
+
+    def _check_fw_status(self):
+        self.lblFwStatus.setText("")
+        self.sliderFwEnable.blockSignals(True)
+        self.comboInput.blockSignals(True)
+        self.comboOutput.blockSignals(True)
+        self.comboProfile.blockSignals(True)
+
+        self._disable_widgets()
+
+        try:
+            enableFw = False
+            enableFwBtn = (self._nodes.count() > 0)
+            self.sliderFwEnable.blockSignals(True)
+            self.sliderFwEnable.setEnabled(enableFwBtn)
+            self.sliderFwEnable.blockSignals(False)
+            if not enableFwBtn:
+                return
+
+            # TODO: handle nodes' firewall properly
+            for addr in self._nodes.get():
+                node = self._nodes.get_node(addr)
+                self._fwConfig = node['firewall']
+                enableFw |= self._fwConfig.Enabled
+
+                if self.fw_is_incompatible(addr, node):
+                    enableFw = False
+                    return
+
+                # XXX: Here we loop twice over the chains. We could have 1 loop.
+                pol_in = self._fw.chains.get_policy(addr, Fw.Hooks.INPUT.value)
+                pol_out = self._fw.chains.get_policy(addr, Fw.Hooks.OUTPUT.value)
+
+                if pol_in != None:
+                    self.comboInput.setCurrentIndex(
+                        Fw.Policy.values().index(pol_in)
+                    )
+                else:
+                    self._set_status_error(QC.translate("firewall", "Error getting INPUT chain policy"))
+                    self._disable_widgets()
+                if pol_out != None:
+                    self.comboOutput.setCurrentIndex(
+                        Fw.Policy.values().index(pol_out)
+                    )
+                else:
+                    self._set_status_error(QC.translate("firewall", "Error getting OUTPUT chain policy"))
+                    self._disable_widgets()
+
+        except Exception as e:
+            self._set_status_error("Firewall status error (report on github please): {0}".format(e))
+
+        finally:
+            # some nodes may have the firewall disabled whilst other enabled
+            #if not enableFw:
+            #    self.lblFwStatus(QC.translate("firewall", "Some nodes have the firewall disabled"))
+
+            self._disable_widgets(not enableFw)
+            self.lblStatusIcon.setEnabled(enableFw)
+            self.sliderFwEnable.setValue(enableFw)
+            self.sliderFwEnable.blockSignals(False)
+            self.comboInput.blockSignals(False)
+            self.comboOutput.blockSignals(False)
+            self.comboProfile.blockSignals(False)
+
+    def fw_is_incompatible(self, addr, node):
+        """Check if the fw is compatible with this GUI.
+        If it's incompatible, disable the option to enable it.
+        """
+        incompatible = False
+        # firewall iptables is not supported from the GUI.
+        # display a warning
+        node_cfg = json.loads(node['data'].config)
+        if node_cfg['Firewall'] == "iptables":
+            self._disable_widgets()
+            self.sliderFwEnable.setEnabled(False)
+            if self.isHidden() == False and self.change_fw(addr, node_cfg):
+                    node_cfg['Firewall'] = "nftables"
+                    self.sliderFwEnable.setEnabled(True)
+                    self.enable_fw(True)
+                    self._change_fw_backend(addr, node_cfg)
+                    return False
+            incompatible = True
+
+        if node['data'].systemFirewall.Version == 0:
+            self._disable_widgets()
+            self.sliderFwEnable.setEnabled(False)
+            self.lblFwStatus.setText(
+                QC.translate("firewall", "<html>The firewall configuration is outdated,\n"
+                            "you need to update it to the new format: <a href=\"{0}\">learn more</a>"
+                            "</html>".format(Config.HELP_SYS_RULES_URL)
+            ))
+            incompatible = True
+
+        return incompatible
+
+    def change_fw(self, addr, node_cfg):
+        """Ask the user to change fw iptables to nftables
+        """
+        ret = Message.yes_no(
+            QC.translate("firewall",
+                        "In order to configure firewall rules from the GUI, we need to use 'nftables' instead of 'iptables'"
+                        ),
+            QC.translate("firewall", "Change default firewall to 'nftables' on node {0}?".format(addr)),
+            QtWidgets.QMessageBox.Warning)
+        if ret != QtWidgets.QMessageBox.Cancel:
+            return True
+
+        return False
+
+    def enable_fw(self, enable):
+        try:
+            self._disable_widgets(not enable)
+            if enable:
+                self._set_status_message(QC.translate("firewall", "Enabling firewall..."))
+            else:
+                self._set_status_message(QC.translate("firewall", "Disabling firewall..."))
+
+            # if previous input policy was DROP, when disabling the firewall it
+            # must be ACCEPT to allow output traffic.
+            if not enable and self.comboInput.currentIndex() == self.POLICY_DROP:
+                self.comboInput.blockSignals(True)
+                self.comboInput.setCurrentIndex(self.POLICY_ACCEPT)
+                self.comboInput.blockSignals(False)
+                for addr in self._nodes.get():
+                    json_profile = json.dumps(FwProfiles.ProfileAcceptInput.value)
+                    ok, err = self._fw.apply_profile(addr, json_profile)
+                    if not ok:
+                        self._set_status_error(
+                            QC.translate("firewall", "Error applying INPUT ACCEPT profile: {0}".format(err))
+                        )
+                        return
+
+            for addr in self._nodes.get():
+                # FIXME:
+                # Due to how the daemon reacts to events when the fw configuration
+                # is modified, changing the policy + disabling the fw doesn't work
+                # as expected.
+                # The daemon detects that the fw is disabled, and it never changes
+                # the policy.
+                # As a workaround to this problem, we send 2 fw changes:
+                # - one for changing the policy
+                # - another one for disabling the fw
+
+                fwcfg = self._nodes.get_node(addr)['firewall']
+                self.send_notification(addr, fwcfg)
+                time.sleep(0.5)
+                fwcfg.Enabled = True if enable else False
+                self.send_notification(addr, fwcfg)
+
+            self.lblStatusIcon.setEnabled(enable)
+            self.policiesBox.setEnabled(enable)
+
+            time.sleep(0.5)
+
+        except Exception as e:
+            QC.translate("firewall", "Error: {0}".format(e))
+
+    def load_rule(self, addr, uuid):
+        self._fwrule_dialog.load(addr, uuid)
+
+    def new_rule(self):
+        self._fwrule_dialog.new()
+
+    def allow_out_service(self):
+        self._fwrule_dialog.exclude_service(self.COMBO_OUT)
+
+    def allow_in_service(self):
+        self._fwrule_dialog.exclude_service(self.COMBO_IN)
+
+    def _set_status_error(self, msg):
+        self.statusLabel.show()
+        self.statusLabel.setStyleSheet('color: red')
+        self.statusLabel.setText(msg)
+
+    def _set_status_successful(self, msg):
+        self.statusLabel.show()
+        self.statusLabel.setStyleSheet('color: green')
+        self.statusLabel.setText(msg)
+
+    def _set_status_message(self, msg):
+        self.statusLabel.show()
+        self.statusLabel.setStyleSheet('color: darkorange')
+        self.statusLabel.setText(msg)
+
+    def _reset_status_message(self):
+        self.statusLabel.setText("")
+        self.statusLabel.hide()
+
+    def _reset_fields(self):
+        self._reset_status_message()
+
+    def _disable_widgets(self, disable=True):
+        self.comboInput.setEnabled(not disable)
+        #self.comboOutput.setEnabled(not disable)
+        self.cmdNewRule.setEnabled(not disable)
+        self.cmdAllowOUTService.setEnabled(not disable)
+        self.cmdAllowINService.setEnabled(not disable)
diff --git a/ui/opensnitch/dialogs/firewall_rule.py b/ui/opensnitch/dialogs/firewall_rule.py
new file mode 100644 (file)
index 0000000..afa85d2
--- /dev/null
@@ -0,0 +1,1573 @@
+import sys
+import os
+import os.path
+import ipaddress
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QCoreApplication as QC
+
+from opensnitch.config import Config
+from opensnitch.nodes import Nodes
+from opensnitch.utils import NetworkServices, NetworkInterfaces, QuickHelp, Icons, Utils
+import opensnitch.firewall as Fw
+from opensnitch.firewall.utils import Utils as FwUtils
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+DIALOG_UI_PATH = "%s/../res/firewall_rule.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class FwRuleDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+    LOG_TAG = "[fw rule dialog]"
+    ACTION_IDX_DENY = 0
+    ACTION_IDX_ALLOW = 1
+
+    IN = 0
+    OUT = 1
+    FORWARD = 2
+    PREROUTING = 3
+    POSTROUTING = 4
+
+    OP_NEW = 0
+    OP_SAVE = 1
+    OP_DELETE = 2
+
+    FORM_TYPE_SIMPLE = 0
+    FORM_TYPE_EXCLUDE_SERVICE = 1
+    FORM_TYPE_ALLOW_IN_SERVICE = 2
+    FORM_TYPE = FORM_TYPE_SIMPLE
+
+    STATM_DPORT = 0
+    STATM_SPORT = 1
+    STATM_DEST_IP = 2
+    STATM_SOURCE_IP = 3
+    STATM_IIFNAME = 4
+    STATM_OIFNAME = 5
+    STATM_CT_SET = 6
+    STATM_CT_MARK = 7
+    STATM_CT_STATE = 8
+    STATM_META_SET_MARK = 9
+    STATM_META = 10
+    STATM_ICMP = 11
+    STATM_ICMPv6 = 12
+    STATM_LOG = 13
+    STATM_QUOTA = 14
+    STATM_COUNTER = 15
+    STATM_LIMIT = 16
+
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+
+    def __init__(self, parent=None, appicon=None):
+        QtWidgets.QDialog.__init__(self, parent)
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+
+        self._fw = Fw.Firewall.instance()
+        self._nodes = Nodes.instance()
+        self.net_srv = NetworkServices()
+        self.statements = {}
+        self.st_num = 0
+
+        self.STATM_LIST = [
+            "",
+            QC.translate("firewall", "Dest Port"),
+            QC.translate("firewall", "Source Port"),
+            QC.translate("firewall", "Dest IP"),
+            QC.translate("firewall", "Source IP"),
+            QC.translate("firewall", "Input interface"),
+            QC.translate("firewall", "Output interface"),
+            QC.translate("firewall", "Set conntrack mark"),
+            QC.translate("firewall", "Match conntrack mark"),
+            QC.translate("firewall", "Match conntrack state(s)"),
+            QC.translate("firewall", "Set mark on packet"),
+            QC.translate("firewall", "Match packet information"),
+            #"TCP",
+            #"UDP",
+            "ICMP",
+            "ICMPv6",
+            "LOG",
+            QC.translate("firewall", "Bandwidth quotas"),
+            "COUNTER",
+            QC.translate("firewall", "Rate limit connections"),
+        ]
+
+        self.STATM_CONF = {
+            self.STATM_DPORT: {
+                'name': Fw.Statements.TCP.value, # tcp, udp, dccp, sctp
+                'tooltip': QC.translate("firewall", """
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+"""),
+                'keys': [
+                    {'key': Fw.Statements.DPORT.value, 'values': self.net_srv.to_array()}
+                ]
+            },
+            self.STATM_SPORT: {
+                'name': Fw.Statements.TCP.value,
+                'tooltip': QC.translate("firewall", """
+Supported formats:
+
+ - Simple: 23
+ - Ranges: 80-1024
+ - Multiple ports: 80,443,8080
+"""),
+                'keys': [
+                    {'key': Fw.Statements.SPORT.value, 'values': self.net_srv.to_array()}
+                ]
+            },
+            self.STATM_DEST_IP: {
+                'name': Fw.Statements.IP.value, # ip or ip6
+                'tooltip': QC.translate("firewall", """
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+"""),
+                'keys': [
+                    {'key': Fw.Statements.DADDR.value, 'values': []}
+                ]
+            },
+            self.STATM_SOURCE_IP: {
+                'name': Fw.Statements.IP.value,
+                'tooltip': QC.translate("firewall", """
+Supported formats:
+
+ - Simple: 1.2.3.4
+ - IP ranges: 1.2.3.100-1.2.3.200
+ - Network ranges: 1.2.3.4/24
+"""),
+                'keys': [
+                    {'key': Fw.Statements.SADDR.value, 'values': []}
+                ]
+            },
+            self.STATM_IIFNAME: {
+                'name': Fw.Statements.IIFNAME.value,
+                'tooltip': QC.translate("firewall", """Match input interface. Regular expressions not allowed.
+Use * to match multiple interfaces."""),
+                'keys': [
+                    {'key': "", 'values': []}
+                ]
+            },
+            self.STATM_OIFNAME: {
+                'name': Fw.Statements.OIFNAME.value,
+                'tooltip': QC.translate("firewall", """Match output interface. Regular expressions not allowed.
+Use * to match multiple interfaces."""),
+                'keys': [
+                    {'key': "", 'values': []}
+                ]
+            },
+            self.STATM_CT_SET: {
+                'name': Fw.Statements.CT.value,
+                'tooltip': QC.translate("firewall", "Set a conntrack mark on the connection, in decimal format."),
+                'keys': [
+                    # we need 2 keys for this expr: key: set, value: <empty>, key: mark, value: xxx
+                    {'key': Fw.ExprCt.SET.value, 'values': None}, # must be empty
+                    {'key': Fw.ExprCt.MARK.value, 'values': []}
+                ]
+            },
+            # match mark
+            self.STATM_CT_MARK: {
+                'name': Fw.Statements.CT.value,
+                'tooltip': QC.translate("firewall", "Match a conntrack mark of the connection, in decimal format."),
+                'keys': [
+                    {'key': Fw.ExprCt.MARK.value, 'values': []}
+                ]
+            },
+            self.STATM_CT_STATE: {
+                'name': Fw.Statements.CT.value,
+                'tooltip': QC.translate("firewall", """Match conntrack states.
+
+Supported formats:
+ - Simple: new
+ - Multiple states separated by commas: related,new
+"""),
+                'keys': [
+                    {
+                        'key': Fw.ExprCt.STATE.value,
+                        'values': [Fw.ExprCt.NEW.value, Fw.ExprCt.ESTABLISHED.value, Fw.ExprCt.RELATED.value, Fw.ExprCt.INVALID.value]
+                    }
+                ]
+            },
+            self.STATM_META: {
+                'name': Fw.Statements.META.value,
+                'tooltip': QC.translate("firewall", """
+Match packet's metainformation.
+
+Value must be in decimal format, except for the "l4proto" option.
+For l4proto it can be a lower case string, for example:
+ tcp
+ udp
+ icmp,
+ etc
+
+If the value is decimal for protocol or lproto, it'll use it as the code of
+that protocol.
+"""),
+                'keys': [
+                    {'key': Fw.ExprMeta.MARK.value, 'values': []},
+                    {'key': Fw.ExprMeta.L4PROTO.value, 'values': Fw.Protocols.values()}
+                ]
+            },
+            self.STATM_META_SET_MARK: {
+                'name': Fw.Statements.META.value,
+                'tooltip': QC.translate("firewall", "Set a mark on the packet matching the specified conditions. The value is in decimal format."),
+                'keys': [
+                    {'key': Fw.ExprMeta.SET.value, 'values': None},
+                    {'key': Fw.ExprMeta.MARK.value, 'values': []}
+                ]
+            },
+            self.STATM_ICMP: {
+                'name': Fw.Statements.ICMP.value,
+                'tooltip': QC.translate("firewall", """
+Match ICMP codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+"""),
+                'keys':  [
+                    {'key': "type", 'values': Fw.ExprICMP.values()}
+                ]
+            },
+            self.STATM_ICMPv6: {
+                'name': Fw.Statements.ICMPv6.value,
+                'tooltip': QC.translate("firewall", """
+Match ICMPv6 codes.
+
+Supported formats:
+ - Simple: echo-request
+ - Multiple separated by commas: echo-request,echo-reply
+"""),
+                'keys':  [
+                    {'key': "type", 'values': Fw.ExprICMP.values()}
+                ]
+            },
+            self.STATM_LOG: {
+                'name': Fw.Statements.LOG.value,
+                'tooltip': QC.translate("firewall", "Print a message when this rule matches a packet."),
+                'keys':  [
+                    {'key': Fw.ExprLog.PREFIX.value, 'values': []}
+                ]
+            },
+            self.STATM_QUOTA: {
+                'name': Fw.ExprQuota.QUOTA.value,
+                'tooltip': QC.translate("firewall", """
+Apply quotas on connections.
+
+For example when:
+ - "quota over 10/mbytes" -> apply the Action defined (DROP)
+ - "quota until 10/mbytes" -> apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS, for example:
+ - 10mbytes, 1/gbytes, etc
+"""),
+                'keys':  [
+                    {'key': Fw.ExprQuota.OVER.value, 'values': []},
+                    {'key': Fw.ExprQuota.UNIT.value, 'values': [
+                        "1/{0}".format(Fw.RateUnits.BYTES.value),
+                        "1/{0}".format(Fw.RateUnits.KBYTES.value),
+                        "1/{0}".format(Fw.RateUnits.MBYTES.value),
+                        "1/{0}".format(Fw.RateUnits.GBYTES.value),
+                    ]}
+                ]
+            },
+            self.STATM_COUNTER: {
+                'name': Fw.ExprCounter.COUNTER.value,
+                'tooltip': QC.translate("firewall", ""),
+                # packets, bytes
+                'keys':  [
+                    {'key': Fw.ExprCounter.PACKETS.value, 'values': None},
+                    {'key': Fw.ExprCounter.NAME.value, 'values': []}
+                ]
+            },
+            # TODO: https://github.com/evilsocket/opensnitch/wiki/System-rules#rules-expressions
+            self.STATM_LIMIT: {
+                'name': Fw.ExprLimit.LIMIT.value,
+                'tooltip': QC.translate("firewall", """
+Apply limits on connections.
+
+For example when:
+ - "limit over 10/mbytes/minute" -> apply the Action defined (DROP, ACCEPT, etc)
+    (When there're more than 10MB per minute, apply an Action)
+
+ - "limit until 10/mbytes/hour" -> apply the Action defined (ACCEPT)
+
+The value must be in the format: VALUE/UNITS/TIME, for example:
+ - 10/mbytes/minute, 1/gbytes/hour, etc
+"""),
+
+                'keys':  [
+                    {'key': Fw.ExprLimit.OVER.value, 'values': []},
+                    {'key': Fw.ExprLimit.UNITS.value, 'values': [
+                        "1/{0}/{1}".format(Fw.RateUnits.BYTES.value, Fw.TimeUnits.SECOND.value),
+                        "1/{0}/{1}".format(Fw.RateUnits.KBYTES.value, Fw.TimeUnits.MINUTE.value),
+                        "1/{0}/{1}".format(Fw.RateUnits.MBYTES.value, Fw.TimeUnits.HOUR.value),
+                        "1/{0}/{1}".format(Fw.RateUnits.GBYTES.value, Fw.TimeUnits.DAY.value),
+                    ]}
+                ]
+            },
+            #self.STATM_TCP: {
+            #    'name': Fw.Statements.TCP.value, # ['dport', 'sport' ... ]
+            #    'key':  Fw.Statements.DADDR.value,
+            #    'values': []
+            #},
+            #self.STATM_UDP: {
+            #    'name': Fw.Statements.UDP.value,
+            #    'key':  Fw.Statements.DADDR.value, # ['dport', 'sport' ... ]
+            #    'values': []
+            #},
+        }
+
+        self._notification_callback.connect(self._cb_notification_callback)
+        self._notifications_sent = {}
+
+        self.uuid = ""
+        self.simple_port_idx = None
+
+        self._nodes.nodesUpdated.connect(self._cb_nodes_updated)
+        self.cmdClose.clicked.connect(self._cb_close_clicked)
+        self.cmdReset.clicked.connect(self._cb_reset_clicked)
+        self.cmdAdd.clicked.connect(self._cb_add_clicked)
+        self.cmdSave.clicked.connect(self._cb_save_clicked)
+        self.cmdDelete.clicked.connect(self._cb_delete_clicked)
+        self.helpButton.clicked.connect(self._cb_help_button_clicked)
+        self.comboVerdict.currentIndexChanged.connect(self._cb_verdict_changed)
+        self.lineVerdictParms.textChanged.connect(self._cb_verdict_parms_changed)
+        self.checkEnable.toggled.connect(self._cb_check_enable_toggled)
+        self.lineDescription.textChanged.connect(self._cb_description_changed)
+
+        self.cmdAddStatement.clicked.connect(self._cb_add_new_statement)
+        self.cmdDelStatement.clicked.connect(self._cb_del_statement)
+        # remove default page
+        self.toolBoxSimple.removeItem(0)
+        self.add_new_statement("", self.toolBoxSimple)
+        self.hboxAdvanced.setVisible(False)
+        # setTabVisible not available on <= 5.14
+        #self.tabWidget.setTabVisible(0, True)
+
+        if QtGui.QIcon.hasThemeIcon("emblem-default"):
+            return
+
+        # -----------------------------------------------------------
+
+        saveIcon = Icons.new(self, "document-save")
+        closeIcon = Icons.new(self, "window-close")
+        delIcon = Icons.new(self, "edit-delete")
+        addIcon = Icons.new(self, "list-add")
+        remIcon = Icons.new(self, "list-remove")
+        helpIcon = Icons.new(self, "help-browser")
+        self.cmdSave.setIcon(saveIcon)
+        self.cmdDelete.setIcon(delIcon)
+        self.cmdClose.setIcon(closeIcon)
+        self.cmdAdd.setIcon(addIcon)
+        self.helpButton.setIcon(helpIcon)
+        self.cmdAddStatement.setIcon(addIcon)
+        self.cmdDelStatement.setIcon(remIcon)
+
+    def show(self):
+        super(FwRuleDialog, self).show()
+        self._reset_fields()
+
+        if FwUtils.isProtobufSupported() == False:
+            self._disable_controls()
+            self._disable_buttons()
+            self._set_status_error(
+                QC.translate(
+                    "firewall",
+                    "Your protobuf version is incompatible, you need to install protobuf 3.8.0 or superior\n(pip3 install --ignore-installed protobuf==3.8.0)"
+                )
+            )
+            return False
+
+        self._load_nodes()
+        self.comboDirection.currentIndexChanged.connect(self._cb_direction_changed)
+        return True
+
+    def _close(self):
+        self.comboDirection.currentIndexChanged.disconnect(self._cb_direction_changed)
+        self.hide()
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def _cb_notification_callback(self, reply):
+        self._enable_buttons()
+
+        try:
+            if reply.id not in self._notifications_sent:
+                return
+
+            rep = self._notifications_sent[reply.id]
+            if reply.code == ui_pb2.OK:
+                if 'operation' in rep and rep['operation'] == self.OP_DELETE:
+                    self.tabWidget.setDisabled(True)
+                    self._set_status_successful(QC.translate("firewall", "Rule deleted"))
+                    self._disable_controls()
+                    del self._notifications_sent[reply.id]
+                    return
+
+                if 'operation' in rep and rep['operation'] == self.OP_SAVE:
+                    self._set_status_successful(QC.translate("firewall", "Rule saved"))
+                else:
+                    self._set_status_successful(QC.translate("firewall", "Rule added"))
+
+            else:
+                # XXX: The errors returned by the nftables lib are not really descriptive.
+                # "invalid argument", "no such file or directory", without context
+                # 1st one: invalid combination of table/chain/priorities?
+                # 2nd one: does the table/chain exist?
+                errormsg = QC.translate("firewall", "Error adding rules:\n{0}".format(reply.data))
+                if 'operation' in rep and rep['operation'] == self.OP_SAVE:
+                    if 'uuid' in rep and rep['uuid'] in reply.data:
+                        errormsg = QC.translate("firewall", "Error saving rule")
+                    else:
+                        self._set_status_message(QC.translate("firewall", "Rule saved, but there're other rules with errors (REVIEW):\n{0}".format(reply.data)))
+                        return
+                self._set_status_error(errormsg)
+
+        except Exception as e:
+            print("[fw rule dialog exception] notif error:", e)
+        finally:
+            if reply.id in self._notifications_sent:
+                del self._notifications_sent[reply.id]
+
+    @QtCore.pyqtSlot(int)
+    def _cb_nodes_updated(self, total):
+        self.tabWidget.setDisabled(True if total == 0 else False)
+
+    def closeEvent(self, e):
+        self._close()
+
+
+    def _cb_check_enable_toggled(self, status):
+        self._enable_save()
+
+    def _cb_description_changed(self, text):
+        self._enable_save()
+
+    def _cb_help_button_clicked(self):
+        QuickHelp.show(
+            QC.translate("firewall",
+                         "You can use ',' or '-' to specify multiple ports/IPs or ranges/values:<br><br>" \
+                         "ports: 22 or 22,443 or 50000-60000<br>" \
+                         "IPs: 192.168.1.1 or 192.168.1.30-192.168.1.130<br>" \
+                         "Values: echo-reply,echo-request<br>" \
+                         "Values: new,established,related"
+                         )
+        )
+
+
+    def _cb_close_clicked(self):
+        self._close()
+
+    def _cb_delete_clicked(self):
+        node_addr, node, chain, err = self.form_to_protobuf()
+        if err != None:
+            self._set_status_error(QC.translate("firewall", "Invalid rule: {0}".format(err)))
+            return
+
+        self._set_status_message(QC.translate("firewall", "Deleting rule, wait"))
+        ok, fw_config = self._fw.delete_rule(node_addr, self.uuid)
+        if not ok:
+            self._set_status_error(QC.translate("firewall", "Error updating rule"))
+            return
+
+        if self.comboNodes.currentIndex() == 0:
+            self.send_notifications(node['firewall'], self.OP_DELETE)
+        else:
+            self.send_notification(node_addr, node['firewall'], self.OP_DELETE)
+
+    def _cb_save_clicked(self):
+        if len(self.statements) == 0:
+            self._set_status_message(QC.translate("firewall", "Add at least one statement."))
+            return
+        node_addr, node, chain, err = self.form_to_protobuf()
+        if err != None:
+            self._set_status_error(QC.translate("firewall", "Invalid rule: {0}".format(err)))
+            return
+
+        self._set_status_message(QC.translate("firewall", "Adding rule, wait"))
+        ok, err = self._fw.update_rule(node_addr, self.uuid, chain)
+        if not ok:
+            self._set_status_error(QC.translate("firewall", "Error updating rule ({0}): {1}".format(node_addr, err)))
+            return
+
+        self._enable_buttons(False)
+        if self.comboNodes.currentIndex() == 0:
+            self.send_notification(node_addr, node['firewall'], self.OP_SAVE, self.uuid)
+        else:
+            self.send_notifications(node['firewall'], self.OP_SAVE)
+
+    def _cb_reset_clicked(self):
+        self._reset_widgets("", self.toolBoxSimple)
+        self.add_new_statement(QC.translate("firewall", "<select a statement>"), self.toolBoxSimple)
+
+    def _cb_add_clicked(self):
+        if len(self.statements) == 0:
+            self._set_status_message(QC.translate("firewall", "Add at least one statement."))
+            return
+        node_addr, node, chain, err = self.form_to_protobuf()
+        if err != None:
+            self._set_status_error(QC.translate("firewall", "Invalid rule: {0}".format(err)))
+            return
+
+        ok, err = self._fw.insert_rule(node_addr, chain)
+        if not ok:
+            self._set_status_error(QC.translate("firewall", "Error adding rule: {0}".format(err)))
+            return
+        self._set_status_message(QC.translate("firewall", "Adding rule, wait"))
+        self._enable_buttons(False)
+
+        if self.comboNodes.currentIndex() == 0:
+            self.send_notification(node_addr, node['firewall'], self.OP_NEW, chain.Rules[0].UUID)
+        else:
+            self.send_notifications(node['firewall'], self.OP_NEW)
+
+    def _cb_add_new_statement(self):
+        self._enable_save()
+        self.add_new_statement(QC.translate("firewall", "<select a statement>"), self.toolBoxSimple)
+
+    def _cb_del_statement(self):
+        self._enable_save()
+
+        idx = self.toolBoxSimple.currentIndex()
+        if idx < 0:
+            return
+
+        if idx in self.statements:
+            del self.statements[idx]
+
+        w = self.toolBoxSimple.widget(idx)
+        if w != None:
+            w.setParent(None)
+
+        self._reorder_toolbox_pages()
+
+    def _cb_statem_combo_changed(self, idx):
+        self._enable_save()
+
+        st_idx = self.toolBoxSimple.currentIndex()
+        self._configure_statem_value_opts(st_idx)
+        w = self.statements[st_idx]
+        tidx = 0 if idx == 0 else idx-1
+        w['value'].setToolTip(self.STATM_CONF[tidx]['tooltip'])
+        self._set_statement_title(st_idx, w['value'].currentText())
+
+    def _cb_statem_value_changed(self, val):
+        self._enable_save()
+
+        st_idx = self.toolBoxSimple.currentIndex()
+        self._set_statement_title(st_idx)
+
+    def _cb_statem_value_index_changed(self, idx):
+        self._enable_save()
+
+        st_idx = self.toolBoxSimple.currentIndex()
+        w = self.statements[st_idx]
+        idx = w['what'].currentIndex()-1 # first item is blank
+        val = w['value'].currentText().lower()
+        if idx != -1 and (idx == self.STATM_SPORT or idx == self.STATM_DPORT):
+            # automagically choose the protocol for the selected port:
+            # echo/7 (tcp) -> tcp
+            if Fw.PortProtocols.TCP.value in val:
+                w['opts'].setCurrentIndex(
+                    Fw.PortProtocols.values().index(Fw.PortProtocols.TCP.value)
+                )
+            elif Fw.PortProtocols.UDP.value in val:
+                w['opts'].setCurrentIndex(
+                    Fw.PortProtocols.values().index(Fw.PortProtocols.UDP.value)
+                )
+        self._set_statement_title(st_idx)
+
+    def _cb_statem_op_changed(self, idx):
+        self._enable_save()
+
+        st_idx = self.toolBoxSimple.currentIndex()
+        self._set_statement_title(st_idx)
+
+    def _cb_statem_opts_changed(self, idx):
+        self._enable_save()
+
+        st_idx = self.toolBoxSimple.currentIndex()
+        self._set_statement_title(st_idx)
+
+    def _cb_direction_changed(self, idx):
+        self._enable_save()
+
+        self._is_valid_rule()
+
+    def _cb_verdict_changed(self, idx):
+        self._enable_save()
+
+        showVerdictParms = self._has_verdict_parms(idx)
+        self.lineVerdictParms.setVisible(showVerdictParms)
+        self.comboVerdictParms.setVisible(showVerdictParms)
+        self._configure_verdict_parms(idx)
+
+
+    def _cb_verdict_parms_changed(self, idx):
+        self._enable_save()
+
+    def _is_valid_rule(self):
+        if (self.comboVerdict.currentText().lower() == Config.ACTION_REDIRECT or \
+            self.comboVerdict.currentText().lower() == Config.ACTION_TPROXY or \
+            self.comboVerdict.currentText().lower() == Config.ACTION_DNAT) and \
+             (self.comboDirection.currentIndex() == self.IN or self.comboDirection.currentIndex() == self.POSTROUTING):
+            self._set_status_message(
+                QC.translate(
+                    "firewall",
+                    "{0} cannot be used with IN or POSTROUTING directions.".format(self.comboVerdict.currentText().upper())
+                )
+            )
+            return False
+        elif self.comboVerdict.currentText().lower() == Config.ACTION_SNAT and \
+             self.comboDirection.currentIndex() != self.POSTROUTING:
+            self._set_status_message(
+                QC.translate(
+                    "firewall",
+                    "{0} can only be used with POSTROUTING.".format(self.comboVerdict.currentText().upper())
+                )
+            )
+            self.comboDirection.setCurrentIndex(self.POSTROUTING)
+            return False
+
+        self._set_status_message("")
+        return True
+
+    def _has_verdict_parms(self, idx):
+        # TODO:
+        # Fw.Verdicts.values()[idx+1] == Config.ACTION_REJECT or \
+        # Fw.Verdicts.values()[idx+1] == Config.ACTION_JUMP or \
+        return Fw.Verdicts.values()[idx+1] == Config.ACTION_QUEUE or \
+            Fw.Verdicts.values()[idx+1] == Config.ACTION_REDIRECT or \
+            Fw.Verdicts.values()[idx+1] == Config.ACTION_TPROXY or \
+            Fw.Verdicts.values()[idx+1] == Config.ACTION_DNAT or \
+            Fw.Verdicts.values()[idx+1] == Config.ACTION_SNAT or \
+            Fw.Verdicts.values()[idx+1] == Config.ACTION_MASQUERADE
+
+    def _configure_verdict_parms(self, idx):
+        self.comboVerdictParms.clear()
+
+        verdict = Fw.Verdicts.values()[idx+1]
+        if verdict == Config.ACTION_QUEUE:
+            self.comboVerdictParms.addItem(QC.translate("firewall", "num"), "num")
+
+        elif verdict == Config.ACTION_JUMP:
+            self.comboVerdictParms.setVisible(False)
+
+        elif verdict == Config.ACTION_REDIRECT or \
+            verdict == Config.ACTION_TPROXY or \
+            verdict == Config.ACTION_SNAT or \
+            verdict == Config.ACTION_DNAT:
+            self.comboVerdictParms.addItem(QC.translate("firewall", "to"), "to")
+
+        elif verdict == Config.ACTION_MASQUERADE:
+            # for persistent,fully-random,etc, options
+            self.comboVerdictParms.addItem("")
+            self.comboVerdictParms.addItem(QC.translate("firewall", "to"), "to")
+
+        # https://wiki.nftables.org/wiki-nftables/index.php/Performing_Network_Address_Translation_(NAT)#Redirect
+        if (verdict == Config.ACTION_REDIRECT or verdict == Config.ACTION_DNAT) and \
+                (self.comboDirection.currentIndex() != self.OUT and self.comboDirection.currentIndex() != self.PREROUTING):
+            self.comboDirection.setCurrentIndex(self.OUT)
+
+        elif self.comboVerdict.currentText().lower() == Config.ACTION_SNAT and \
+             self.comboDirection.currentIndex() != self.POSTROUTING:
+            self.comboDirection.setCurrentIndex(self.POSTROUTING)
+
+    def _reorder_toolbox_pages(self):
+        tmp = {}
+        for i,k in enumerate(self.statements):
+            tmp[i] = self.statements[k]
+        self.statements = tmp
+
+    def _reset_widgets(self, title, topWidget):
+        for i in range(topWidget.count()):
+            topWidget.removeItem(i)
+            w = topWidget.widget(i)
+            if w is not None:
+                w.setParent(None)
+
+        self.statements = {}
+        self.st_num = 0
+
+        # if we don't do this, toolbox's subwidgets are not deleted (removed
+        # from the GUI, but not deleted), so sometimes after loading/closing several rules,
+        # you may end up with rules mixed on the same layout/form.
+        self.toolBoxSimple.setParent(None)
+        self.toolBoxSimple = QtWidgets.QToolBox()
+        self.tabWidget.widget(0).layout().addWidget(self.toolBoxSimple)
+        #self.toolBoxSimple.currentChanged.connect(self._cb_toolbox_page_changed)
+
+    def _set_statement_title(self, st_idx, value=None):
+        """Transform the widgets to nftables rule text format
+        """
+        self._reset_status_message()
+        self.toolBoxSimple.setItemText(st_idx, "")
+        w = self.statements[st_idx]
+        idx = w['what'].currentIndex()-1 # first item is blank
+        if idx == -1:
+            return
+
+        st = self.STATM_CONF[idx]['name']
+        st_opts = w['opts'].currentText()
+        if idx == self.STATM_DEST_IP or idx == self.STATM_SOURCE_IP:
+            st = st_opts
+        if idx == self.STATM_DPORT or idx == self.STATM_SPORT:
+            st = st_opts
+
+        title = st
+        for keys in self.STATM_CONF[idx]['keys']:
+            title += " " + keys['key']
+        st_op = Fw.Operator.values()[w['op'].currentIndex()]
+        st_val = w['value'].currentText()
+        if value != None:
+            st_val = value
+
+        # override previous setup for some statements
+        if idx == self.STATM_META:
+            title = "{0} {1} {2} {3}".format(st, st_opts, st_op, st_val)
+        elif idx == self.STATM_QUOTA:
+            title = "{0} {1} {2}".format(st, st_opts, st_val)
+        elif idx == self.STATM_LIMIT:
+            title = "{0} {1} {2}".format(st, st_opts, st_val)
+        else:
+            title = "{0} {1} {2}".format(title, st_op, st_val)
+
+        self.toolBoxSimple.setItemText(st_idx, title)
+
+    def _configure_statem_value_opts(self, st_idx):
+        w = self.statements[st_idx]
+        idx = w['what'].currentIndex()-1 # first item is blank
+        if idx == -1:
+            return
+
+        w['value'].blockSignals(True);
+        w['opts'].blockSignals(True);
+
+        oldValue = w['value'].currentText()
+        w['value'].clear()
+        for k in self.STATM_CONF[idx]['keys']:
+            if k['values'] == None:
+                continue
+            w['value'].addItems(k['values'])
+        w['value'].setCurrentText(oldValue)
+
+        w['opts'].clear()
+        if idx == self.STATM_DPORT or \
+            idx == self.STATM_SPORT:
+            w['op'].setVisible(True)
+            w['opts'].setVisible(True)
+            w['opts'].addItems(Fw.PortProtocols.values())
+
+        elif idx == self.STATM_DEST_IP or \
+            idx == self.STATM_SOURCE_IP:
+            w['op'].setVisible(True)
+            w['opts'].setVisible(True)
+            w['opts'].addItems(Fw.Family.values())
+            w['opts'].removeItem(0) # remove 'inet' item
+
+        elif idx == self.STATM_IIFNAME or idx == self.STATM_OIFNAME:
+            w['op'].setVisible(True)
+            w['opts'].setVisible(False)
+            if self._nodes.is_local(self.comboNodes.currentText()):
+                w['value'].addItems(NetworkInterfaces.list().keys())
+                w['value'].setCurrentText("")
+
+        elif idx == self.STATM_META:
+            w['op'].setVisible(True)
+            w['opts'].setVisible(True)
+            # exclude first item of the list
+            w['opts'].addItems(Fw.ExprMeta.values()[1:])
+
+        elif idx == self.STATM_ICMP or idx == self.STATM_ICMPv6 or \
+            idx == self.STATM_CT_STATE or idx == self.STATM_CT_MARK:
+            w['op'].setVisible(True)
+            w['opts'].setVisible(False)
+
+        elif idx == self.STATM_LOG:
+            w['op'].setVisible(False)
+            w['opts'].setVisible(True)
+            w['opts'].addItems(Fw.ExprLogLevels.values())
+            w['opts'].setCurrentIndex(
+                # nftables default log level is warn
+                Fw.ExprLogLevels.values().index(Fw.ExprLogLevels.WARN.value)
+            )
+        elif idx == self.STATM_QUOTA or idx == self.STATM_LIMIT:
+            w['op'].setVisible(False)
+            w['opts'].setVisible(True)
+            w['opts'].addItems([Fw.ExprQuota.OVER.value, Fw.ExprQuota.UNTIL.value])
+        else:
+            w['op'].setVisible(False)
+            w['opts'].setVisible(False)
+
+        w['opts'].blockSignals(False);
+        w['value'].blockSignals(False);
+
+    def add_new_statement(self, title="", topWidget=None):
+        """Creates dynamically the widgets to define firewall rules:
+            statement (dst port, dst ip, log), protocol, operator (==, !=,...)
+            and value (443, 1.1.1.1, etc)
+            Some expressions may have different options (protocol, family, options, etc)
+        """
+        w = QtWidgets.QWidget()
+        w.setParent(topWidget)
+        l = QtWidgets.QVBoxLayout(w)
+
+        boxH1 = QtWidgets.QHBoxLayout()
+        boxH2 = QtWidgets.QHBoxLayout()
+        l.addLayout(boxH1)
+        l.addLayout(boxH2)
+
+        # row 1: | statement | protocol |
+        stWidget = QtWidgets.QComboBox(w)
+        stWidget.addItems(self.STATM_LIST)
+
+        prots = ["TCP", "UDP", "ICMP"]
+        stOptsWidget = QtWidgets.QComboBox(w)
+        stOptsWidget.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+        stOptsWidget.addItems(prots)
+
+        # row 2: | operator | value |
+        ops = [
+            QC.translate("firewall", "Equal"),
+            QC.translate("firewall", "Not equal"),
+            QC.translate("firewall", "Greater or equal than"),
+            QC.translate("firewall", "Greater than"),
+            QC.translate("firewall", "Less or equal than"),
+            QC.translate("firewall", "Less than")
+        ]
+        stOpWidget = QtWidgets.QComboBox(w)
+        stOpWidget.setSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+        stOpWidget.addItems(ops)
+
+        stValueWidget = QtWidgets.QComboBox(w)
+        stValueWidget.setEditable(True)
+        stValueWidget.setSizePolicy(QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed)
+        stValueWidget.setCurrentText("")
+
+        # add statement, proto/opts, operator and value
+        boxH1.addWidget(stWidget)
+        boxH1.addWidget(stOptsWidget)
+        boxH2.addWidget(stOpWidget)
+        boxH2.addWidget(stValueWidget)
+        w.setLayout(l)
+
+        # insert page after current index
+        curIdx = self.toolBoxSimple.currentIndex()
+        topWidget.insertItem(curIdx+1, w, title)
+        topWidget.setCurrentIndex(curIdx+1)
+
+        # if current index is not the last one, reorder statements
+        if curIdx+1 != self.st_num:
+            for i in range(curIdx+1, self.st_num):
+                if i in self.statements:
+                    self.statements[i+1] = self.statements[i]
+
+        self.statements[curIdx+1] = {
+            'what': stWidget,
+            'opts': stOptsWidget,
+            'op': stOpWidget,
+            'value': stValueWidget
+        }
+
+        stWidget.currentIndexChanged.connect(self._cb_statem_combo_changed)
+        stOpWidget.currentIndexChanged.connect(self._cb_statem_op_changed)
+        stOptsWidget.currentIndexChanged.connect(self._cb_statem_opts_changed)
+        stValueWidget.currentIndexChanged.connect(self._cb_statem_value_index_changed)
+        stValueWidget.currentTextChanged.connect(self._cb_statem_value_changed)
+
+        self.st_num += 1
+
+    def _load_nodes(self):
+        self.comboNodes.clear()
+        self._node_list = self._nodes.get()
+        #self.comboNodes.addItem(QC.translate("firewall", "All"))
+        for addr in self._node_list:
+            self.comboNodes.addItem(addr)
+
+        if len(self._node_list) == 0:
+            self.tabWidget.setDisabled(True)
+
+    def _load_meta_statement(self, exp, idx):
+        try:
+            isMultiProto = False
+            isSetMark = False
+            newStatm = self.STATM_SPORT
+            newValue = ""
+            optsValue = ""
+            for v in exp.Statement.Values:
+                if v.Key ==  Fw.ExprMeta.SET.value:
+                    isSetMark = True
+                    continue
+                if isSetMark and v.Key == Fw.ExprMeta.MARK.value:
+                    newStatm = self.STATM_META_SET_MARK
+                    if self._is_valid_int_value(v.Value):
+                        newValue = v.Value
+                    else:
+                        self._set_status_error(
+                            QC.translate(
+                                "firewall",
+                                "Invalid mark ({0})".format(v.Value)
+                            )
+                        )
+                    break
+
+                if v.Key ==  Fw.ExprMeta.L4PROTO.value:
+                    optsValue = v.Value
+                if v.Key == Fw.Statements.SPORT.value:
+                    isMultiProto = True
+                    newValue = v.Value
+                    break
+                elif v.Key == Fw.Statements.DPORT.value:
+                    newStatm = self.STATM_DPORT
+                    isMultiProto = True
+                    newValue = v.Value
+                    break
+
+            if isSetMark:
+                self.statements[idx]['what'].setCurrentIndex(newStatm+1)
+                self.statements[idx]['value'].setCurrentText(newValue)
+
+            elif isMultiProto:
+                self.statements[idx]['what'].setCurrentIndex(newStatm+1)
+                self.statements[idx]['opts'].setCurrentIndex(
+                    Fw.PortProtocols.values().index(optsValue)
+                )
+                try:
+                    self.statements[idx]['value'].setCurrentIndex(
+                        self.net_srv.index_by_port(newValue)
+                    )
+                except:
+                    self.statements[idx]['value'].setCurrentText(newValue)
+
+            else:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_META+1)
+                self.statements[idx]['opts'].setCurrentIndex(
+                    # first item of the list is "set", not present in the combobox
+                    Fw.ExprMeta.values().index(exp.Statement.Values[0].Key)-1
+                )
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Value)
+
+        except Exception as e:
+            print("_load_meta_statement() exception:", e)
+            self._set_status_message(e)
+
+    def _load_limit_statement(self, exp, idx):
+        try:
+            self.statements[idx]['what'].setCurrentIndex(self.STATM_LIMIT+1)
+            self.statements[idx]['opts'].setCurrentIndex(1)
+            lval = ""
+            for v in exp.Statement.Values:
+                if v.Key == Fw.ExprLimit.OVER.value:
+                    self.statements[idx]['opts'].setCurrentIndex(0)
+                elif v.Key == Fw.ExprLimit.UNITS.value:
+                    lval = v.Value
+                elif v.Key == Fw.ExprLimit.RATE_UNITS.value:
+                    lval = "%s/%s" % (lval, v.Value)
+                elif v.Key == Fw.ExprLimit.TIME_UNITS.value:
+                    lval = "%s/%s" % (lval, v.Value)
+
+            self.statements[idx]['value'].setCurrentText(lval)
+        except Exception as e:
+            print("_load_limit_statement() exception:", e)
+            self._set_status_message(e)
+
+    def _load_ct_statement(self, exp, idx):
+        """load CT statements, for example:
+            Name: ct, Key: set, Key: mark, Value: 123
+            Name: ct, Key: mark, Value: 123
+            Name: ct, Key: state, value: new,established
+        """
+        try:
+            if exp.Statement.Values[0].Key == Fw.ExprCt.STATE.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_CT_STATE+1)
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Value)
+                for v in exp.Statement.Values:
+                    curText = self.statements[idx]['value'].currentText()
+                    if v.Value not in curText:
+                        self.statements[idx]['value'].setCurrentText(
+                            "{0},{1}".format(
+                                curText,
+                                v.Value
+                            )
+                        )
+
+            elif exp.Statement.Values[0].Key == Fw.ExprCt.SET.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_CT_SET+1)
+                markVal = ""
+                for v in exp.Statement.Values:
+                    if v.Key == Fw.ExprCt.MARK.value:
+                        markVal = v.Value
+                        break
+
+                self.statements[idx]['value'].setCurrentText(markVal)
+                if markVal == "":
+                    raise ValueError(
+                        QC.translate("firewall", "Warning: ct set mark value is empty, malformed rule?")
+                    )
+
+            elif exp.Statement.Values[0].Key == Fw.ExprCt.MARK.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_CT_MARK+1)
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Value)
+
+        except Exception as e:
+            print("_load_ct_statement() exception:", e)
+            self._set_status_message(e)
+
+    def load(self, addr, uuid):
+        if not self.show():
+            return
+
+        self.FORM_TYPE = self.FORM_TYPE_SIMPLE
+        self.setWindowTitle(QC.translate("firewall", "Firewall rule"))
+        self.cmdDelete.setVisible(True)
+        self.cmdSave.setVisible(True)
+        self.cmdAdd.setVisible(False)
+        self.checkEnable.setVisible(True)
+        self.checkEnable.setEnabled(True)
+        self.checkEnable.setChecked(True)
+        self.frameDirection.setVisible(True)
+        self.comboNodes.setCurrentText(addr)
+
+        self._enable_buttons()
+
+        self.uuid = uuid
+
+        node, rule = self._fw.get_rule_by_uuid(uuid)
+        if rule == None or \
+                (rule.Hook.lower() != Fw.Hooks.INPUT.value and \
+                 rule.Hook.lower() != Fw.Hooks.FORWARD.value and \
+                 rule.Hook.lower() != Fw.Hooks.PREROUTING.value and \
+                 rule.Hook.lower() != Fw.Hooks.POSTROUTING.value and \
+                 rule.Hook.lower() != Fw.Hooks.OUTPUT.value):
+            hook = "invalid" if rule == None else rule.Hook
+            self._set_status_error(QC.translate("firewall", "Rule hook ({0}) not supported yet".format(hook)))
+            self._disable_controls()
+            return
+
+        self.checkEnable.setChecked(rule.Rules[0].Enabled)
+        self.lineDescription.setText(rule.Rules[0].Description)
+
+        self.tabWidget.blockSignals(True);
+        self.hboxAdvanced.setVisible(True)
+        self._reset_widgets("", self.toolBoxSimple)
+        self.tabWidget.setCurrentIndex(0)
+
+        if len(rule.Rules[0].Expressions) <= 1:
+            self.tabWidget.setTabText(0, QC.translate("firewall", "Simple"))
+            self.add_new_statement("", self.toolBoxSimple)
+        else:
+            for i in enumerate(rule.Rules[0].Expressions):
+                self.add_new_statement("", self.toolBoxSimple)
+            self.tabWidget.setTabText(0, QC.translate("firewall", "Advanced"))
+
+        self.tabWidget.blockSignals(False);
+
+        isNotSupported = False
+        idx = 0
+        for exp in rule.Rules[0].Expressions:
+            #print(idx, "|", exp)
+
+            # set current page, so the title and opts of each statement is
+            # configured properly.
+            self.toolBoxSimple.setCurrentIndex(idx)
+
+            if Fw.Utils.isExprPort(exp.Statement.Name):
+                if exp.Statement.Values[0].Key == Fw.Statements.DPORT.value:
+                    self.statements[idx]['what'].setCurrentIndex(self.STATM_DPORT+1)
+                elif exp.Statement.Values[0].Key == Fw.Statements.SPORT.value:
+                    self.statements[idx]['what'].setCurrentIndex(self.STATM_SPORT+1)
+
+                try:
+                    self.statements[idx]['value'].setCurrentIndex(
+                        self.net_srv.index_by_port(exp.Statement.Values[0].Value)
+                    )
+                except:
+                    self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Value)
+
+                st_name = exp.Statement.Name
+                self.statements[idx]['opts'].setCurrentIndex(
+                    Fw.PortProtocols.values().index(st_name.lower())
+                )
+
+            elif exp.Statement.Name == Fw.Statements.IP.value or exp.Statement.Name == Fw.Statements.IP6.value:
+                if exp.Statement.Values[0].Key == Fw.Statements.DADDR.value:
+                    self.statements[idx]['what'].setCurrentIndex(self.STATM_DEST_IP+1)
+                elif exp.Statement.Values[0].Key == Fw.Statements.SADDR.value:
+                    self.statements[idx]['what'].setCurrentIndex(self.STATM_SOURCE_IP+1)
+
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Value)
+
+                st_name = exp.Statement.Name
+                self.statements[idx]['opts'].setCurrentIndex(
+                    Fw.Family.values().index(st_name.lower())-1 # first item does not apply
+                )
+
+            elif exp.Statement.Name == Fw.Statements.IIFNAME.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_IIFNAME+1)
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Key)
+
+            elif exp.Statement.Name == Fw.Statements.OIFNAME.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_OIFNAME+1)
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Key)
+
+            elif exp.Statement.Name == Fw.Statements.CT.value:
+                self._load_ct_statement(exp, idx)
+
+            elif exp.Statement.Name == Fw.Statements.META.value:
+                self._load_meta_statement(exp, idx)
+
+            elif exp.Statement.Name == Fw.Statements.ICMP.value or exp.Statement.Name == Fw.Statements.ICMPv6.value:
+                if exp.Statement.Name == Fw.Statements.ICMP.value:
+                    self.statements[idx]['what'].setCurrentIndex(self.STATM_ICMP+1)
+                else:
+                    self.statements[idx]['what'].setCurrentIndex(self.STATM_ICMPv6+1)
+
+                self.statements[idx]['value'].setCurrentText(exp.Statement.Values[0].Value)
+                for v in exp.Statement.Values:
+                    curText = self.statements[idx]['value'].currentText()
+                    if v.Value not in curText:
+                        self.statements[idx]['value'].setCurrentText(
+                            "{0},{1}".format(
+                                curText,
+                                v.Value
+                            )
+                        )
+
+            elif exp.Statement.Name == Fw.Statements.LOG.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_LOG+1)
+
+                for v in exp.Statement.Values:
+                    if v.Key == Fw.ExprLog.PREFIX.value:
+                        self.statements[idx]['value'].setCurrentText(v.Value)
+                    elif v.Key == Fw.ExprLog.LEVEL.value:
+                        try:
+                            lvl = Fw.ExprLogLevels.values().index(v.Value)
+                        except:
+                            lvl = Fw.ExprLogLevels.values().index(Fw.ExprLogLevels.WARN.value)
+                        self.statements[idx]['opts'].setCurrentIndex(lvl)
+
+            elif exp.Statement.Name == Fw.Statements.QUOTA.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_QUOTA+1)
+                self.statements[idx]['opts'].setCurrentIndex(1)
+                for v in exp.Statement.Values:
+                    if v.Key == Fw.ExprQuota.OVER.value:
+                        self.statements[idx]['opts'].setCurrentIndex(0)
+                    else:
+                        self.statements[idx]['value'].setCurrentText(
+                            "{0}/{1}".format(v.Value, v.Key)
+                        )
+
+            elif exp.Statement.Name == Fw.Statements.LIMIT.value:
+                self._load_limit_statement(exp, idx)
+
+            elif exp.Statement.Name == Fw.Statements.COUNTER.value:
+                self.statements[idx]['what'].setCurrentIndex(self.STATM_COUNTER+1)
+                for v in exp.Statement.Values:
+                    if v.Key == Fw.ExprCounter.NAME.value:
+                        self.statements[idx]['value'].setCurrentText(v.Value)
+
+            else:
+                isNotSupported = True
+                break
+
+            # a statement may not have an operator. It's assumed that it's the
+            # equal operator.
+            op = Fw.Operator.EQUAL.value if exp.Statement.Op == "" else exp.Statement.Op
+            self.statements[idx]['op'].setCurrentIndex(
+                Fw.Operator.values().index(op)
+            )
+
+            idx+=1
+
+        if isNotSupported:
+            self._set_status_error(QC.translate("firewall", "This rule is not supported yet."))
+            self._disable_controls()
+            return
+
+        if rule.Hook.lower() == Fw.Hooks.INPUT.value:
+            self.comboDirection.setCurrentIndex(self.IN)
+        elif rule.Hook.lower() == Fw.Hooks.OUTPUT.value:
+            self.comboDirection.setCurrentIndex(self.OUT)
+        elif rule.Hook.lower() == Fw.Hooks.FORWARD.value:
+            self.comboDirection.setCurrentIndex(self.FORWARD)
+        elif rule.Hook.lower() == Fw.Hooks.PREROUTING.value:
+            self.comboDirection.setCurrentIndex(self.PREROUTING)
+        elif rule.Hook.lower() == Fw.Hooks.POSTROUTING.value:
+            self.comboDirection.setCurrentIndex(self.POSTROUTING)
+        # TODO: changing the direction of an existed rule needs work, it causes
+        # some nasty effects. Disabled for now.
+        self.comboDirection.setEnabled(False)
+
+        try:
+            self.comboVerdict.setCurrentIndex(
+                Fw.Verdicts.values().index(
+                    rule.Rules[0].Target.lower()
+                )-1
+            )
+            if self._has_verdict_parms(self.comboVerdict.currentIndex()):
+                tparms = rule.Rules[0].TargetParameters.lower()
+                parts = tparms.split(" ")
+                self.lineVerdictParms.setText(parts[1])
+                if parts[1] == "":
+                    print("Firewall Rule: verdict parms error:", parts)
+        except:
+            self._set_status_error(QC.translate("firewall", "Rule target ({0}) not supported yet".format(rule.Rules[0].Target.lower())))
+            self._disable_controls()
+
+        self._enable_save(False)
+
+    def new(self):
+        if not self.show():
+            return
+
+        self._reset_widgets("", self.toolBoxSimple)
+        self.FORM_TYPE = self.FORM_TYPE_SIMPLE
+        self.setWindowTitle(QC.translate("firewall", "Firewall rule"))
+        self.cmdDelete.setVisible(False)
+        self.cmdSave.setVisible(False)
+        self.cmdAdd.setVisible(True)
+        self.checkEnable.setVisible(True)
+        self.checkEnable.setEnabled(True)
+        self.checkEnable.setChecked(True)
+        self.frameDirection.setVisible(True)
+
+        self.cmdSave.setVisible(False)
+        self.cmdDelete.setVisible(False)
+        self.cmdAdd.setVisible(True)
+
+        self.hboxAdvanced.setVisible(True)
+        self.tabWidget.setTabText(0, "")
+        self.tabWidget.setCurrentIndex(0)
+        self.add_new_statement("", self.toolBoxSimple)
+
+    def exclude_service(self, direction):
+        if not self.show():
+            return
+
+        self._reset_widgets("", self.toolBoxSimple)
+        self.setWindowTitle(QC.translate("firewall", "Exclude service"))
+        self.cmdDelete.setVisible(False)
+        self.cmdSave.setVisible(False)
+        self.cmdReset.setVisible(False)
+        self.cmdAdd.setVisible(True)
+        self.checkEnable.setVisible(False)
+        self.checkEnable.setEnabled(True)
+        self.tabWidget.setTabText(0, "")
+        self.hboxAdvanced.setVisible(False)
+
+        dirPort = self.STATM_DPORT+1
+        self.FORM_TYPE = self.FORM_TYPE_ALLOW_IN_SERVICE
+        self.lblExcludeTip.setText(QC.translate("firewall", "Allow inbound connections to the selected port."))
+        if direction == self.OUT:
+            self.lblExcludeTip.setText(QC.translate("firewall", "Allow outbound connections to the selected port."))
+            self.FORM_TYPE = self.FORM_TYPE_EXCLUDE_SERVICE
+            dirPort = self.STATM_DPORT+1
+
+        self.add_new_statement("", self.toolBoxSimple)
+        self.statements[0]['what'].setCurrentIndex(dirPort)
+        self.statements[0]['what'].setVisible(False)
+        self.statements[0]['op'].setVisible(False)
+        self.statements[0]['value'].setCurrentText("")
+
+        self.frameDirection.setVisible(False)
+        self.lblExcludeTip.setVisible(True)
+
+        self.checkEnable.setChecked(True)
+
+    def form_to_protobuf(self):
+        """Transform form widgets to protobuf struct
+        """
+        chain = Fw.ChainFilter.input()
+        # XXX: tproxy does not work with destnat+output
+        if self.comboDirection.currentIndex() == self.OUT and \
+            (self.comboVerdict.currentIndex()+1 == Fw.Verdicts.values().index(Config.ACTION_TPROXY) or \
+                self.comboVerdict.currentIndex()+1 == Fw.Verdicts.values().index(Config.ACTION_DNAT) or \
+                self.comboVerdict.currentIndex()+1 == Fw.Verdicts.values().index(Config.ACTION_REDIRECT)
+            ):
+            chain = Fw.ChainDstNAT.output()
+        elif self.comboDirection.currentIndex() == self.FORWARD:
+            chain = Fw.ChainMangle.forward()
+        elif self.comboDirection.currentIndex() == self.PREROUTING:
+            chain = Fw.ChainDstNAT.prerouting()
+        elif self.comboDirection.currentIndex() == self.POSTROUTING:
+            chain = Fw.ChainDstNAT.postrouting()
+
+        elif self.comboDirection.currentIndex() == self.OUT or self.FORM_TYPE == self.FORM_TYPE_EXCLUDE_SERVICE:
+            chain = Fw.ChainMangle.output()
+        elif self.comboDirection.currentIndex() == self.IN or self.FORM_TYPE == self.FORM_TYPE_ALLOW_IN_SERVICE:
+            chain = Fw.ChainFilter.input()
+
+        verdict_idx = self.comboVerdict.currentIndex()
+        verdict = Fw.Verdicts.values()[verdict_idx+1] # index 0 is ""
+        _target_parms = ""
+        if self._has_verdict_parms(verdict_idx):
+            if self.lineVerdictParms.text() == "":
+                return None, None, None, QC.translate("firewall", "Verdict ({0}) parameters cannot be empty.".format(verdict))
+
+            # these verdicts parameters need ":" to specify a port or ip:port
+            if (self.comboVerdict.currentText().lower() == Config.ACTION_REDIRECT or \
+                self.comboVerdict.currentText().lower() == Config.ACTION_TPROXY or \
+                self.comboVerdict.currentText().lower() == Config.ACTION_SNAT or \
+                self.comboVerdict.currentText().lower() == Config.ACTION_DNAT) and \
+                    ":" not in self.lineVerdictParms.text():
+                return None, None, None, QC.translate("firewall", "Verdict ({0}) parameters format is: <IP>:port.".format(verdict))
+
+            if self.comboVerdict.currentText().lower() == Config.ACTION_QUEUE:
+                try:
+                    t = int(self.lineVerdictParms.text())
+                except:
+                    return None, None, None, QC.translate("firewall", "Verdict ({0}) parameters format must be a number".format(verdict))
+
+            vidx = self.comboVerdictParms.currentIndex()
+            _target_parms = "{0} {1}".format(
+                self.comboVerdictParms.itemData(vidx),
+                self.lineVerdictParms.text().replace(" ", "")
+            )
+
+        rule = Fw.Rules.new(
+            enabled=self.checkEnable.isChecked(),
+            _uuid=self.uuid,
+            description=self.lineDescription.text(),
+            target=verdict,
+            target_parms=_target_parms
+        )
+
+        for k in self.statements:
+            st_idx = self.statements[k]['what'].currentIndex()-1
+            if st_idx == -1:
+                return None, None, None, QC.translate("firewall", "select a statement.")
+
+            statement = self.STATM_CONF[st_idx]['name']
+            statem_keys = self.STATM_CONF[st_idx]['keys']
+            statem_op = Fw.Operator.values()[self.statements[k]['op'].currentIndex()]
+            statem_opts = self.statements[k]['opts'].currentText().lower()
+
+            key_values = []
+            for sk in statem_keys:
+                if sk['values'] == None:
+                    key_values.append((sk['key'], ""))
+                else:
+                    statem_value = self.statements[k]['value'].currentText()
+                    val_idx = self.statements[k]['value'].currentIndex()
+
+                    if statem_value == "" or (statem_value == "0" and st_idx != self.STATM_META):
+                        return None, None, None, QC.translate("firewall", "value cannot be 0 or empty.")
+
+                    if st_idx == self.STATM_QUOTA:
+                        if sk['key'] == Fw.ExprQuota.OVER.value:
+                            if self.statements[k]['opts'].currentIndex() == 0:
+                                key_values.append((sk['key'], ""))
+                            continue
+                        elif sk['key'] == Fw.ExprQuota.UNIT.value or sk['key'] in Fw.RateUnits.values():
+                            units = statem_value.split("/")
+                            if len(units) != 2: # we expect the format key/value
+                                return None, None, None, QC.translate("firewall", "the value format is 1024/kbytes (or bytes, mbytes, gbytes)")
+                            if units[1] not in Fw.RateUnits.values():
+                                return None, None, None, QC.translate("firewall", "the value format is 1024/kbytes (or bytes, mbytes, gbytes)")
+
+                            sk['key'] = units[1]
+                            statem_value = units[0]
+                            if not self._is_valid_int_value(statem_value):
+                                raise ValueError("quota value is invalid ({0}). It must be value/unit (1/kbytes)".format(statem_value))
+
+                    elif st_idx == self.STATM_LIMIT:
+                        if sk['key'] == Fw.ExprLimit.OVER.value:
+                            if self.statements[k]['opts'].currentIndex() == 0:
+                                key_values.append((sk['key'], ""))
+                        elif sk['key'] == Fw.ExprLimit.UNITS.value:
+                            units = statem_value.split("/")
+                            if len(units) != 3: # we expect the format key/value
+                                return None, None, None, QC.translate("firewall", "the value format is 1024/kbytes/second (or bytes, mbytes, gbytes)")
+
+                            if units[1] not in Fw.RateUnits.values():
+                                return None, None, None, QC.translate("firewall", "rate-limit not valid, use: bytes, kbytes, mbytes or gbytes.")
+                            if units[2] not in Fw.TimeUnits.values():
+                                return None, None, None, QC.translate("firewall", "time-limit not valid, use: second, minute, hour or day")
+                            key_values.append((Fw.ExprLimit.UNITS.value, units[0]))
+                            key_values.append((Fw.ExprLimit.RATE_UNITS.value, units[1]))
+                            key_values.append((Fw.ExprLimit.TIME_UNITS.value, units[2]))
+
+                        continue
+
+                    elif st_idx == self.STATM_LOG:
+                        key_values.append((Fw.ExprLog.LEVEL.value, statem_opts))
+
+                    elif st_idx == self.STATM_META:
+                        sk['key'] = self.statements[k]['opts'].currentText()
+
+                    elif st_idx == self.STATM_IIFNAME or st_idx == self.STATM_OIFNAME:
+                        # for these statements, the values is set in the Key
+                        # field instead of Value. Value must be empty
+                        sk['key'] = statem_value
+                        statem_value = ""
+
+                    elif st_idx == self.STATM_DEST_IP or st_idx == self.STATM_SOURCE_IP:
+                        statement = statem_opts
+                        # convert network u.x.y.z/nn to 1.2.3.4-1.255.255.255
+                        # format.
+                        # FIXME: This should be supported by the daemon,
+                        # instead of converting it here.
+                        # TODO: validate IP ranges.
+                        if "/" in statem_value:
+                            try:
+                                net = ipaddress.ip_network(statem_value)
+                                hosts = list(net)
+                                statem_value = "{0}-{1}".format(str(hosts[0]), str(hosts[-1]))
+                            except Exception as e:
+                                return None, None, None, QC.translate("firewall", "IP network format error, {0}".format(e))
+                        elif not "-" in statem_value:
+                            try:
+                                ipaddress.ip_address(statem_value)
+                            except Exception as e:
+                                return None, None, None, QC.translate("firewall", "{0}".format(e))
+
+                    elif st_idx == self.STATM_DPORT or st_idx == self.STATM_SPORT:
+                        # if it's a tcp+udp port, we need to add a meta+l4proto
+                        # statement, with the protos + ports as values.
+                        optsIdx = self.statements[k]['opts'].currentIndex()
+                        isMultiProto = optsIdx == 0
+                        if isMultiProto:
+                            meta = self.STATM_CONF[self.STATM_META]['keys'][1]
+                            statement = self.STATM_CONF[self.STATM_META]['name']
+                            # key: l4proto
+                            key_values.append((meta['key'], statem_opts))
+
+                        else:
+                            statement = statem_opts
+
+                        # 1. if the value is one of the /etc/services return
+                        # the port
+                        # 2. if the value contains , or - just use the written
+                        # value, to allow multiple ports and ranges.
+                        # 3. otherwise validate that the entered value is an
+                        # int
+                        try:
+                            if "," in statem_value or "-" in statem_value:
+                                raise ValueError("port entered is multiport or a port range")
+                            service_idx = self.net_srv.service_by_name(statem_value)
+                            statem_value = self.net_srv.port_by_index(service_idx)
+                        except:
+                            if "," not in statem_value and "-" not in statem_value:
+                                if not self._is_valid_int_value(statem_value):
+                                    return None, None, None, QC.translate("firewall", "port not valid.")
+
+                    elif st_idx == self.STATM_CT_SET or st_idx == self.STATM_CT_MARK or st_idx == self.STATM_META_SET_MARK:
+                        if not self._is_valid_int_value(statem_value):
+                            return None, None, None, QC.translate("firewall", "Invalid value {0}, number expected.".format(statem_value))
+
+                    elif st_idx == self.STATM_ICMP or st_idx == self.STATM_ICMPv6:
+                        values = statem_value.split(",")
+                        for val in values:
+                            if val not in Fw.ExprICMP.values():
+                                return None, None, None, QC.translate("firewall", "Invalid ICMP type \"{0}\".".format(val))
+
+                    keyVal = (sk['key'], statem_value.replace(" ", ""))
+                    if keyVal not in key_values:
+                        key_values.append(keyVal)
+                    else:
+                        print("[REVIEW] statement values duplicated (there shouldn't be):", keyVal)
+
+            exprs = Fw.Expr.new(
+                statem_op,
+                statement,
+                key_values,
+            )
+            rule.Expressions.extend([exprs])
+        chain.Rules.extend([rule])
+
+        node_addr = self.comboNodes.currentText()
+        node = self._nodes.get_node(node_addr)
+        return node_addr, node, chain, None
+
+    def _is_valid_int_value(self, value):
+        try:
+            int(value)
+        except:
+            return False
+
+        return True
+
+    def send_notification(self, node_addr, fw_config, op, uuid):
+        nid, notif = self._nodes.reload_fw(node_addr, fw_config, self._notification_callback)
+        self._notifications_sent[nid] = {'addr': node_addr, 'operation': op, 'notif': notif, 'uuid': uuid}
+
+    def send_notifications(self, fw_config, op):
+        for addr in self._nodes.get_nodes():
+            nid, notif = self._nodes.reload_fw(addr, fw_config, self._notification_callback)
+            self._notifications_sent[nid] = {'addr': addr, 'operation': op, 'notif': notif}
+
+    def _set_status_error(self, msg):
+        self.statusLabel.show()
+        self.statusLabel.setStyleSheet('color: red')
+        self.statusLabel.setText(msg)
+
+    def _set_status_successful(self, msg):
+        self.statusLabel.show()
+        self.statusLabel.setStyleSheet('color: green')
+        self.statusLabel.setText(msg)
+
+    def _set_status_message(self, msg):
+        self.statusLabel.show()
+        self.statusLabel.setStyleSheet('color: darkorange')
+        self.statusLabel.setText(msg)
+
+    def _reset_status_message(self):
+        self.statusLabel.setText("")
+        self.statusLabel.hide()
+
+    def _reset_fields(self):
+        self.FORM_TYPE = self.FORM_TYPE_SIMPLE
+        self.setWindowTitle(QC.translate("firewall", "Firewall rule"))
+
+        self.cmdDelete.setVisible(False)
+        self.cmdSave.setVisible(False)
+        self.cmdAdd.setVisible(True)
+
+        self.checkEnable.setVisible(True)
+        self.checkEnable.setEnabled(True)
+        self.checkEnable.setChecked(True)
+        self.frameDirection.setVisible(True)
+        self.lblExcludeTip.setVisible(False)
+        self.lblExcludeTip.setText("")
+
+        self._reset_status_message()
+        self._enable_buttons()
+        self.tabWidget.setDisabled(False)
+        self.lineDescription.setText("")
+        self.comboDirection.setCurrentIndex(self.IN)
+        self.comboDirection.setEnabled(True)
+
+        self.comboVerdict.blockSignals(True);
+        self.comboVerdict.setCurrentIndex(0)
+        self.comboVerdict.blockSignals(False);
+        self.lineVerdictParms.setVisible(False)
+        self.comboVerdictParms.setVisible(False)
+        self.lineVerdictParms.setText("")
+
+        self.uuid = ""
+
+    def _enable_save(self, enable=True):
+        """Enable Save buton whenever some detail of a route changes.
+        The button may or not be hidden. If we're editing a rule it'll be shown
+        but disabled/enabled.
+        """
+        self.cmdSave.setEnabled(enable)
+
+    def _enable_buttons(self, enable=True):
+        """Disable add/save buttons until a response is received from the daemon.
+        """
+        self.cmdSave.setEnabled(enable)
+        self.cmdAdd.setEnabled(enable)
+        self.cmdDelete.setEnabled(enable)
+
+    def _disable_buttons(self, disabled=True):
+        self.cmdSave.setDisabled(disabled)
+        self.cmdAdd.setDisabled(disabled)
+        self.cmdDelete.setDisabled(disabled)
+
+    def _disable_controls(self):
+        self._disable_buttons()
+        self.tabWidget.setDisabled(True)
diff --git a/ui/opensnitch/dialogs/preferences.py b/ui/opensnitch/dialogs/preferences.py
new file mode 100644 (file)
index 0000000..bcdf176
--- /dev/null
@@ -0,0 +1,1049 @@
+import sys
+import time
+import os
+import json
+import stat
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QCoreApplication as QC
+
+from opensnitch.config import Config
+from opensnitch.nodes import Nodes
+from opensnitch.database import Database
+from opensnitch.utils import Message, QuickHelp, Themes, Icons, languages
+from opensnitch.utils.xdg import Autostart
+from opensnitch.notifications import DesktopNotifications
+
+from opensnitch import auth
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+DIALOG_UI_PATH = "%s/../res/preferences.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class PreferencesDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+
+    LOG_TAG = "[Preferences] "
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+    saved = QtCore.pyqtSignal()
+
+    TAB_POPUPS = 0
+    TAB_UI = 1
+    TAB_RULES = 2
+    TAB_NODES = 3
+    TAB_DB = 4
+
+    NODE_PAGE_GENERAL = 0
+    NODE_PAGE_LOGGING = 1
+    NODE_PAGE_AUTH = 2
+
+    SUM = 1
+    REST = 0
+
+    AUTH_SIMPLE = 0
+    AUTH_TLS_SIMPLE = 1
+    AUTH_TLS_MUTUAL = 2
+
+    NODE_AUTH = {
+        AUTH_SIMPLE: auth.Simple,
+        AUTH_TLS_SIMPLE: auth.TLSSimple,
+        AUTH_TLS_MUTUAL: auth.TLSMutual
+    }
+    NODE_AUTH_VERIFY = {
+        0: auth.NO_CLIENT_CERT,
+        1: auth.REQ_CERT,
+        2: auth.REQ_ANY_CERT,
+        3: auth.VERIFY_CERT,
+        4: auth.REQ_AND_VERIFY_CERT
+    }
+
+    def __init__(self, parent=None, appicon=None):
+        QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
+
+        self._themes = Themes.instance()
+        self._saved_theme = ""
+        self._restart_msg = QC.translate("preferences", "Restart the GUI in order changes to take effect")
+        self._changes_needs_restart = None
+
+        self._cfg = Config.get()
+        self._nodes = Nodes.instance()
+        self._db = Database.instance()
+        self._autostart = Autostart()
+
+        self._notification_callback.connect(self._cb_notification_callback)
+        self._notifications_sent = {}
+        self._desktop_notifications = DesktopNotifications()
+
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+
+        self.checkDBMaxDays.setEnabled(True)
+        self.dbFileButton.setVisible(False)
+        self.dbLabel.setVisible(False)
+        self.dbType = None
+
+        intValidator = QtGui.QDoubleValidator(0, 20, 2, self)
+        self.lineUIScreenFactor.setValidator(intValidator)
+
+        self.acceptButton.clicked.connect(self._cb_accept_button_clicked)
+        self.applyButton.clicked.connect(self._cb_apply_button_clicked)
+        self.cancelButton.clicked.connect(self._cb_cancel_button_clicked)
+        self.helpButton.clicked.connect(self._cb_help_button_clicked)
+        self.popupsCheck.clicked.connect(self._cb_popups_check_toggled)
+        self.dbFileButton.clicked.connect(self._cb_file_db_clicked)
+        self.cmdTimeoutUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUITimeout, self.SUM))
+        self.cmdTimeoutDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUITimeout, self.REST))
+        self.cmdRefreshUIUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUIRefresh, self.SUM))
+        self.cmdRefreshUIDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUIRefresh, self.REST))
+        self.cmdUIDensityUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUIDensity, self.SUM))
+        self.cmdUIDensityDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinUIDensity, self.REST))
+        self.cmdDBMaxDaysUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBMaxDays, self.SUM))
+        self.cmdDBMaxDaysDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBMaxDays, self.REST))
+        self.cmdDBPurgesUp.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBPurgeInterval, self.SUM))
+        self.cmdDBPurgesDown.clicked.connect(lambda: self._cb_cmd_spin_clicked(self.spinDBPurgeInterval, self.REST))
+        self.cmdTestNotifs.clicked.connect(self._cb_test_notifs_clicked)
+        self.radioSysNotifs.clicked.connect(self._cb_radio_system_notifications)
+        self.helpButton.setToolTipDuration(30 * 1000)
+
+        self.comboAuthType.currentIndexChanged.connect(self._cb_combo_auth_type_changed)
+        self.comboAuthType.setItemData(PreferencesDialog.AUTH_SIMPLE, auth.Simple)
+        self.comboAuthType.setItemData(PreferencesDialog.AUTH_TLS_SIMPLE, auth.TLSSimple)
+        self.comboAuthType.setItemData(PreferencesDialog.AUTH_TLS_MUTUAL, auth.TLSMutual)
+        self.comboNodeAuthType.setItemData(PreferencesDialog.AUTH_SIMPLE, auth.Simple)
+        self.comboNodeAuthType.setItemData(PreferencesDialog.AUTH_TLS_SIMPLE, auth.TLSSimple)
+        self.comboNodeAuthType.setItemData(PreferencesDialog.AUTH_TLS_MUTUAL, auth.TLSMutual)
+        self.comboNodeAuthVerifyType.setItemData(0, auth.NO_CLIENT_CERT)
+        self.comboNodeAuthVerifyType.setItemData(1, auth.REQ_CERT)
+        self.comboNodeAuthVerifyType.setItemData(2, auth.REQ_ANY_CERT)
+        self.comboNodeAuthVerifyType.setItemData(3, auth.VERIFY_CERT)
+        self.comboNodeAuthVerifyType.setItemData(4, auth.REQ_AND_VERIFY_CERT)
+
+        self.comboUIRules.currentIndexChanged.connect(self._cb_combo_uirules_changed)
+
+        if QtGui.QIcon.hasThemeIcon("emblem-default"):
+            return
+
+        saveIcon = Icons.new(self, "document-save")
+        applyIcon = Icons.new(self, "emblem-default")
+        delIcon = Icons.new(self, "edit-delete")
+        closeIcon = Icons.new(self, "window-close")
+        openIcon = Icons.new(self, "document-open")
+        helpIcon = Icons.new(self, "help-browser")
+        addIcon = Icons.new(self, "list-add")
+        delIcon = Icons.new(self, "list-remove")
+        allowIcon = Icons.new(self, "emblem-default")
+        denyIcon = Icons.new(self, "emblem-important")
+        rejectIcon = Icons.new(self, "window-close")
+        self.applyButton.setIcon(applyIcon)
+        self.cancelButton.setIcon(closeIcon)
+        self.acceptButton.setIcon(saveIcon)
+        self.helpButton.setIcon(helpIcon)
+        self.dbFileButton.setIcon(openIcon)
+
+        self.cmdTimeoutUp.setIcon(addIcon)
+        self.cmdTimeoutDown.setIcon(delIcon)
+        self.cmdRefreshUIUp.setIcon(addIcon)
+        self.cmdRefreshUIDown.setIcon(delIcon)
+        self.cmdUIDensityUp.setIcon(addIcon)
+        self.cmdUIDensityDown.setIcon(delIcon)
+        self.cmdDBMaxDaysUp.setIcon(addIcon)
+        self.cmdDBMaxDaysDown.setIcon(delIcon)
+        self.cmdDBPurgesUp.setIcon(addIcon)
+        self.cmdDBPurgesDown.setIcon(delIcon)
+
+        self.comboUIAction.setItemIcon(Config.ACTION_DENY_IDX, denyIcon)
+        self.comboUIAction.setItemIcon(Config.ACTION_ALLOW_IDX, allowIcon)
+        self.comboUIAction.setItemIcon(Config.ACTION_REJECT_IDX, rejectIcon)
+
+    def showEvent(self, event):
+        super(PreferencesDialog, self).showEvent(event)
+
+        try:
+            self._changes_needs_restart = None
+            self._settingsSaved = False
+            self._reset_status_message()
+            self._hide_status_label()
+            self.comboNodes.clear()
+
+            self._load_langs()
+
+            self.comboNodeAddress.clear()
+            run_path = "/run/user/{0}/opensnitch/".format(os.getuid())
+            var_run_path = "/var{0}".format(run_path)
+            self.comboNodeAddress.addItem("unix:///tmp/osui.sock")
+            if os.path.exists(run_path):
+                self.comboNodeAddress.addItem("unix://%s/osui.sock" % run_path)
+            if os.path.exists(var_run_path):
+                self.comboNodeAddress.addItem("unix://%s/osui.sock" % var_run_path)
+
+            self._node_list = self._nodes.get()
+            for addr in self._node_list:
+                self.comboNodes.addItem(addr)
+
+            if len(self._node_list) == 0:
+                self._reset_node_settings()
+                self._set_status_message(QC.translate("preferences", "There're no nodes connected"))
+        except Exception as e:
+            print(self.LOG_TAG + "exception loading nodes:", e)
+
+        self._load_settings()
+
+        # connect the signals after loading settings, to avoid firing
+        # the signals
+        self.comboNodes.currentIndexChanged.connect(self._cb_node_combo_changed)
+        self.comboNodeAction.currentIndexChanged.connect(self._cb_node_needs_update)
+        self.comboNodeDuration.currentIndexChanged.connect(self._cb_node_needs_update)
+        self.comboNodeMonitorMethod.currentIndexChanged.connect(self._cb_node_needs_update)
+        self.comboNodeLogLevel.currentIndexChanged.connect(self._cb_node_needs_update)
+        self.comboNodeLogFile.currentIndexChanged.connect(self._cb_node_needs_update)
+        self.checkNodeLogUTC.clicked.connect(self._cb_node_needs_update)
+        self.checkNodeLogMicro.clicked.connect(self._cb_node_needs_update)
+        self.comboNodeAddress.currentTextChanged.connect(self._cb_node_needs_update)
+        self.checkInterceptUnknown.clicked.connect(self._cb_node_needs_update)
+        self.checkApplyToNodes.clicked.connect(self._cb_node_needs_update)
+        self.comboNodeAction.currentIndexChanged.connect(self._cb_node_needs_update)
+        self.checkNodeAuthSkipVerify.clicked.connect(self._cb_node_needs_update)
+        self.comboNodeAuthVerifyType.currentIndexChanged.connect(self._cb_node_needs_update)
+
+        self.comboAuthType.currentIndexChanged.connect(self._cb_combo_auth_type_changed)
+        self.comboNodeAuthType.currentIndexChanged.connect(self._cb_combo_node_auth_type_changed)
+
+        self.lineCACertFile.textChanged.connect(self._cb_line_certs_changed)
+        self.lineCertFile.textChanged.connect(self._cb_line_certs_changed)
+        self.lineCertKeyFile.textChanged.connect(self._cb_line_certs_changed)
+        self.lineNodeCACertFile.textChanged.connect(self._cb_node_line_certs_changed)
+        self.lineNodeCertFile.textChanged.connect(self._cb_node_line_certs_changed)
+        self.lineNodeCertKeyFile.textChanged.connect(self._cb_node_line_certs_changed)
+
+        self.lineUIScreenFactor.textChanged.connect(self._cb_ui_screen_factor_changed)
+        self.checkUIRules.toggled.connect(self._cb_ui_check_rules_toggled)
+        self.checkUIAutoScreen.toggled.connect(self._cb_ui_check_auto_scale_toggled)
+        self.comboUITheme.currentIndexChanged.connect(self._cb_combo_themes_changed)
+        self.spinUIDensity.valueChanged.connect(self._cb_spin_uidensity_changed)
+
+        self.comboDBType.currentIndexChanged.connect(self._cb_db_type_changed)
+        self.checkDBMaxDays.toggled.connect(self._cb_db_max_days_toggled)
+        self.checkDBJrnlWal.toggled.connect(self._cb_db_jrnl_wal_toggled)
+
+        # True when any node option changes
+        self._node_needs_update = False
+
+    def show_node_prefs(self, addr):
+        self.show()
+        self.comboNodes.setCurrentText(addr)
+        self.tabWidget.setCurrentIndex(self.TAB_NODES)
+
+    def _load_langs(self):
+        try:
+            self.comboUILang.clear()
+            self.comboUILang.blockSignals(True)
+            self.comboUILang.addItem(QC.translate("preferences", "System default"), "")
+            langs, langNames = languages.get_all()
+            for idx, lang in enumerate(langs):
+                self.comboUILang.addItem(langNames[idx].capitalize(), langs[idx])
+            self.comboUILang.blockSignals(False)
+        except Exception as e:
+            print(self.LOG_TAG + "exception loading languages:", e)
+
+    def _load_themes(self):
+        self.comboUITheme.blockSignals(True)
+        theme_idx, self._saved_theme, theme_density = self._themes.get_saved_theme()
+
+        self.labelThemeError.setVisible(False)
+        self.labelThemeError.setText("")
+        self.comboUITheme.clear()
+        self.comboUITheme.addItem(QC.translate("preferences", "System"))
+        if self._themes.available():
+            themes = self._themes.list_themes()
+            self.comboUITheme.addItems(themes)
+        else:
+            self._saved_theme = ""
+            self.labelThemeError.setStyleSheet('color: red')
+            self.labelThemeError.setVisible(True)
+            self.labelThemeError.setText(QC.translate("preferences", "Themes not available. Install qt-material: pip3 install qt-material"))
+
+        self.comboUITheme.setCurrentIndex(theme_idx)
+        self._show_ui_density_widgets(theme_idx)
+        try:
+            self.spinUIDensity.setValue(int(theme_density))
+        except Exception as e:
+            print("load_theme() invalid theme density scale:", theme_density, ":", e)
+
+        self.comboUITheme.blockSignals(False)
+
+    def _load_settings(self):
+        self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+        self._default_target = self._cfg.getInt(self._cfg.DEFAULT_TARGET_KEY, 0)
+        self._default_timeout = self._cfg.getInt(self._cfg.DEFAULT_TIMEOUT_KEY, Config.DEFAULT_TIMEOUT)
+        self._disable_popups = self._cfg.getBool(self._cfg.DEFAULT_DISABLE_POPUPS)
+
+        if self._cfg.hasKey(self._cfg.DEFAULT_DURATION_KEY):
+            self._default_duration = self._cfg.getInt(self._cfg.DEFAULT_DURATION_KEY)
+        else:
+            self._default_duration = self._cfg.DEFAULT_DURATION_IDX
+
+        self.comboUIDuration.setCurrentIndex(self._default_duration)
+        self.comboUIDialogPos.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION))
+        self.comboUIAction.setCurrentIndex(self._default_action)
+        self.comboUITarget.setCurrentIndex(self._default_target)
+        self.spinUITimeout.setValue(self._default_timeout)
+        self.spinUITimeout.setEnabled(not self._disable_popups)
+        self.popupsCheck.setChecked(self._disable_popups)
+
+        self.showAdvancedCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED))
+        self.dstIPCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP))
+        self.dstPortCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT))
+        self.uidCheck.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_UID))
+
+        maxmsgsize = self._cfg.getSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH)
+        if maxmsgsize:
+            self.comboGrpcMsgSize.setCurrentText(maxmsgsize)
+        else:
+            self.comboGrpcMsgSize.setCurrentIndex(0)
+
+        self.lineCACertFile.setText(self._cfg.getSettings(Config.AUTH_CA_CERT))
+        self.lineCertFile.setText(self._cfg.getSettings(Config.AUTH_CERT))
+        self.lineCertKeyFile.setText(self._cfg.getSettings(Config.AUTH_CERTKEY))
+        authtype_idx = self.comboAuthType.findData(self._cfg.getSettings(Config.AUTH_TYPE))
+        if authtype_idx <= 0:
+            authtype_idx = 0
+            self.lineCACertFile.setEnabled(False)
+            self.lineCertFile.setEnabled(False)
+            self.lineCertKeyFile.setEnabled(False)
+        self.comboAuthType.setCurrentIndex(authtype_idx)
+
+        self.comboUIRules.blockSignals(True)
+        self.comboUIRules.setCurrentIndex(self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES))
+        self.checkUIRules.setChecked(self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES))
+        self.comboUIRules.setEnabled(self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES))
+
+        #self._set_rules_duration_filter()
+
+        self._cfg.setRulesDurationFilter(
+            self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES),
+            self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES)
+        )
+        self.comboUIRules.blockSignals(False)
+
+         # by default, if no configuration exists, enable notifications.
+        self.groupNotifs.setChecked(self._cfg.getBool(Config.NOTIFICATIONS_ENABLED, True))
+        self.radioSysNotifs.setChecked(
+            True if self._cfg.getInt(Config.NOTIFICATIONS_TYPE) == Config.NOTIFICATION_TYPE_SYSTEM and self._desktop_notifications.is_available() == True else False
+        )
+        self.radioQtNotifs.setChecked(
+            True if self._cfg.getInt(Config.NOTIFICATIONS_TYPE) == Config.NOTIFICATION_TYPE_QT or self._desktop_notifications.is_available() == False else False
+        )
+
+        ## db
+        self.dbType = self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY)
+        self.comboDBType.setCurrentIndex(self.dbType)
+        if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY:
+            self.dbFileButton.setVisible(True)
+            self.dbLabel.setVisible(True)
+            self.dbLabel.setText(self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY))
+        dbMaxDays = self._cfg.getInt(self._cfg.DEFAULT_DB_MAX_DAYS, 1)
+        dbJrnlWal = self._cfg.getBool(self._cfg.DEFAULT_DB_JRNL_WAL)
+        dbPurgeInterval = self._cfg.getInt(self._cfg.DEFAULT_DB_PURGE_INTERVAL, 5)
+        self._enable_db_cleaner_options(self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST), dbMaxDays)
+        self._enable_db_jrnl_wal(self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST), dbJrnlWal)
+        self.spinDBMaxDays.setValue(dbMaxDays)
+        self.spinDBPurgeInterval.setValue(dbPurgeInterval)
+
+        self._load_themes()
+        self._load_node_settings()
+        self._load_ui_settings()
+
+    def _load_ui_settings(self):
+        self._ui_refresh_interval = self._cfg.getInt(self._cfg.STATS_REFRESH_INTERVAL, 0)
+        self.spinUIRefresh.setValue(self._ui_refresh_interval)
+
+        saved_lang = self._cfg.getSettings(Config.DEFAULT_LANGUAGE)
+        if saved_lang:
+            saved_langname = self._cfg.getSettings(Config.DEFAULT_LANGNAME)
+            self.comboUILang.blockSignals(True)
+            self.comboUILang.setCurrentText(saved_langname)
+            self.comboUILang.blockSignals(False)
+
+        auto_scale = self._cfg.getBool(Config.QT_AUTO_SCREEN_SCALE_FACTOR, default_value=True)
+        screen_factor = self._cfg.getSettings(Config.QT_SCREEN_SCALE_FACTOR)
+        if screen_factor is None or screen_factor == "":
+            screen_factor = "1"
+        self.lineUIScreenFactor.setText(screen_factor)
+        self.checkUIAutoScreen.blockSignals(True)
+        self.checkUIAutoScreen.setChecked(auto_scale)
+        self.checkUIAutoScreen.blockSignals(False)
+        self._show_ui_scalefactor_widgets(auto_scale)
+
+        qt_platform = self._cfg.getSettings(Config.QT_PLATFORM_PLUGIN)
+        if qt_platform is not None and qt_platform != "":
+            self.comboUIQtPlatform.setCurrentText(qt_platform)
+
+        self.checkAutostart.setChecked(self._autostart.isEnabled())
+
+        self._load_ui_columns_config()
+
+    def _load_node_settings(self):
+        addr = self.comboNodes.currentText()
+        if addr == "":
+            return
+
+        try:
+            node_data = self._node_list[addr]['data']
+            self.labelNodeVersion.setText(node_data.version)
+            self.labelNodeName.setText(node_data.name)
+            self.comboNodeLogLevel.setCurrentIndex(node_data.logLevel)
+
+            node_config = json.loads(node_data.config)
+            self.comboNodeAction.setCurrentText(node_config['DefaultAction'])
+            self.comboNodeDuration.setCurrentText(node_config['DefaultDuration'])
+            self.comboNodeMonitorMethod.setCurrentText(node_config['ProcMonitorMethod'])
+            self.checkInterceptUnknown.setChecked(node_config['InterceptUnknown'])
+            self.comboNodeLogLevel.setCurrentIndex(int(node_config['LogLevel']))
+
+            if node_config.get('LogUTC') == None:
+                node_config['LogUTC'] = False
+            self.checkNodeLogUTC.setChecked(node_config['LogUTC'])
+            if node_config.get('LogMicro') == None:
+                node_config['LogMicro'] = False
+            self.checkNodeLogMicro.setChecked(node_config['LogMicro'])
+
+            if node_config.get('Server') != None:
+                self.comboNodeAddress.setEnabled(True)
+                self.comboNodeLogFile.setEnabled(True)
+
+                self.comboNodeAddress.setCurrentText(node_config['Server']['Address'])
+                self.comboNodeLogFile.setCurrentText(node_config['Server']['LogFile'])
+
+                self._load_node_auth_settings(node_config['Server'])
+            else:
+                self.comboNodeAddress.setEnabled(False)
+                self.comboNodeLogFile.setEnabled(False)
+        except Exception as e:
+            print(self.LOG_TAG + "exception loading config: ", e)
+
+    def _load_node_config(self, addr):
+        try:
+            if self.comboNodeAddress.currentText() == "":
+                return None, QC.translate("preferences", "Server address can not be empty")
+
+            node_action = Config.ACTION_DENY
+            if self.comboNodeAction.currentIndex() == 1:
+                node_action = Config.ACTION_ALLOW
+            elif self.comboNodeAction.currentIndex() == 2:
+                node_action = Config.ACTION_REJECT
+
+            node_duration = Config.DURATION_ONCE
+            if self.comboNodeDuration.currentIndex() == 1:
+                node_duration = Config.DURATION_UNTIL_RESTART
+            elif self.comboNodeDuration.currentIndex() == 2:
+                node_duration = Config.DURATION_ALWAYS
+
+            node_config = json.loads(self._nodes.get_node_config(addr))
+            node_config['DefaultAction'] = node_action
+            node_config['DefaultDuration'] = node_duration
+            node_config['ProcMonitorMethod'] = self.comboNodeMonitorMethod.currentText()
+            node_config['LogLevel'] = self.comboNodeLogLevel.currentIndex()
+            node_config['LogUTC'] = self.checkNodeLogUTC.isChecked()
+            node_config['LogMicro'] = self.checkNodeLogMicro.isChecked()
+            node_config['InterceptUnknown'] = self.checkInterceptUnknown.isChecked()
+
+            if node_config.get('Server') != None:
+                # skip setting Server Address if we're applying the config to all nodes
+                node_config['Server']['Address'] = self.comboNodeAddress.currentText()
+                node_config['Server']['LogFile'] = self.comboNodeLogFile.currentText()
+
+                cfg = self._save_node_auth_config(node_config['Server'])
+                if cfg != None:
+                    node_config['Server'] = cfg
+            else:
+                print(addr, " doesn't have Server item")
+            return json.dumps(node_config, indent="    "), None
+        except Exception as e:
+            print(self.LOG_TAG + "exception loading node config on %s: " % addr, e)
+
+        return None, QC.translate("preferences", "Error loading {0} configuration").format(addr)
+
+    def _load_node_auth_settings(self, config):
+        try:
+            if config == None:
+                return
+
+            auth = config.get('Authentication')
+            authtype_idx = 0
+            if auth != None:
+                if auth.get('Type') != None:
+                    authtype_idx = self.comboNodeAuthType.findData(auth['Type'])
+            else:
+                config['Authentication'] = {}
+                auth = config.get('Authentication')
+
+            self.lineNodeCACertFile.setEnabled(authtype_idx >= 0)
+            self.lineNodeServerCertFile.setEnabled(authtype_idx >= 0)
+            self.lineNodeCertFile.setEnabled(authtype_idx >= 0)
+            self.lineNodeCertKeyFile.setEnabled(authtype_idx >= 0)
+
+            tls = auth.get('TLSOptions')
+            if tls != None and authtype_idx >= 0:
+                if tls.get('CACert') != None:
+                    self.lineNodeCACertFile.setText(tls['CACert'])
+                if tls.get('ServerCert') != None:
+                    self.lineNodeServerCertFile.setText(tls['ServerCert'])
+                if tls.get('ClientCert') != None:
+                    self.lineNodeCertFile.setText(tls['ClientCert'])
+                if tls.get('ClientKey') != None:
+                    self.lineNodeCertKeyFile.setText(tls['ClientKey'])
+                if tls.get('SkipVerify') != None:
+                    self.checkNodeAuthSkipVerify.setChecked(tls['SkipVerify'])
+
+                if tls.get('ClientAuthType') != None:
+                    clienttype_idx = self.comboNodeAuthVerifyType.findData(tls['ClientAuthType'])
+                    if clienttype_idx >= 0:
+                        self.comboNodeAuthVerifyType.setCurrentIndex(clienttype_idx)
+
+            self.comboNodeAuthType.setCurrentIndex(authtype_idx)
+            # signals are connected after this method is called
+            self._cb_combo_node_auth_type_changed(authtype_idx)
+        except Exception as e:
+            print("[prefs] load node auth options exception:", e)
+            self._set_status_error(str(e))
+
+    def _save_node_auth_config(self, config):
+        try:
+            auth = config.get('Authentication')
+            if auth == None:
+                auth = {}
+
+            auth['Type'] = self.NODE_AUTH[self.comboNodeAuthType.currentIndex()]
+            tls = auth.get('TLSOptions')
+            if tls == None:
+                tls = {}
+
+            tls['CACert'] = self.lineNodeCACertFile.text()
+            tls['ServerCert'] = self.lineNodeServerCertFile.text()
+            tls['ClientCert'] = self.lineNodeCertFile.text()
+            tls['ClientKey'] = self.lineNodeCertKeyFile.text()
+            tls['SkipVerify'] = self.checkNodeAuthSkipVerify.isChecked()
+            tls['ClientAuthType'] = self.NODE_AUTH_VERIFY[self.comboNodeAuthVerifyType.currentIndex()]
+            auth['TLSOptions'] = tls
+            config['Authentication'] = auth
+
+            return config
+        except Exception as e:
+            print("[prefs] node auth options exception:", e)
+            self._set_status_error(str(e))
+            return None
+
+    def _load_ui_columns_config(self):
+        cols = self._cfg.getSettings(Config.STATS_SHOW_COLUMNS)
+        if cols == None:
+            return
+
+        for c in range(13):
+            checked = str(c) in cols
+
+            if c == 0:
+                self.checkHideTime.setChecked(checked)
+            elif c == 1:
+                self.checkHideNode.setChecked(checked)
+            elif c == 2:
+                self.checkHideAction.setChecked(checked)
+            elif c == 3:
+                self.checkHideSrcPort.setChecked(checked)
+            elif c == 4:
+                self.checkHideSrcIP.setChecked(checked)
+            elif c == 5:
+                self.checkHideDstIP.setChecked(checked)
+            elif c == 6:
+                self.checkHideDstHost.setChecked(checked)
+            elif c == 7:
+                self.checkHideDstPort.setChecked(checked)
+            elif c == 8:
+                self.checkHideProto.setChecked(checked)
+            elif c == 9:
+                self.checkHideUID.setChecked(checked)
+            elif c == 10:
+                self.checkHidePID.setChecked(checked)
+            elif c == 11:
+                self.checkHideProc.setChecked(checked)
+            elif c == 12:
+                self.checkHideCmdline.setChecked(checked)
+            elif c == 13:
+                self.checkHideRule.setChecked(checked)
+
+    def _reset_node_settings(self):
+        self.comboNodeAction.setCurrentIndex(0)
+        self.comboNodeDuration.setCurrentIndex(0)
+        self.comboNodeMonitorMethod.setCurrentIndex(0)
+        self.checkInterceptUnknown.setChecked(False)
+        self.comboNodeLogLevel.setCurrentIndex(0)
+        self.checkNodeLogUTC.setChecked(True)
+        self.checkNodeLogMicro.setChecked(False)
+        self.labelNodeName.setText("")
+        self.labelNodeVersion.setText("")
+        self.comboNodeAuthType.setCurrentIndex(self.AUTH_SIMPLE)
+        self.lineNodeCACertFile.setText("")
+        self.lineNodeServerCertFile.setText("")
+        self.lineNodeCertFile.setText("")
+        self.lineNodeCertKeyFile.setText("")
+        self.checkNodeAuthSkipVerify.setChecked(False)
+        self.comboNodeAuthVerifyType.setCurrentIndex(0)
+        self._cb_combo_node_auth_type_changed(0)
+
+    def _save_settings(self):
+        self._reset_status_message()
+        self._show_status_label()
+        self._save_ui_config()
+        if not self._save_db_config():
+            return
+        self._save_nodes_config()
+
+        self.saved.emit()
+        self._settingsSaved = True
+        self._needs_restart()
+
+    def _save_db_config(self):
+        dbtype = self.comboDBType.currentIndex()
+        db_name = self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY)
+
+        if self.dbLabel.text() != "" and \
+                (self.comboDBType.currentIndex() != self.dbType or db_name != self.dbLabel.text()):
+            self._changes_needs_restart = QC.translate("preferences", "DB type changed")
+
+        if self.comboDBType.currentIndex() != Database.DB_TYPE_MEMORY:
+            if self.dbLabel.text() != "":
+                db_name = self.dbLabel.text()
+            else:
+                Message.ok(
+                    QC.translate("preferences", "Warning"),
+                    QC.translate("preferences", "You must select a file for the database<br>or choose \"In memory\" type."),
+                    QtWidgets.QMessageBox.Warning)
+                self.dbLabel.setText("")
+                return False
+        else:
+            db_name = Database.DB_IN_MEMORY
+
+        self._cfg.setSettings(Config.DEFAULT_DB_FILE_KEY, db_name)
+        self._cfg.setSettings(Config.DEFAULT_DB_TYPE_KEY, dbtype)
+        self._cfg.setSettings(Config.DEFAULT_DB_PURGE_OLDEST, bool(self.checkDBMaxDays.isChecked()))
+        self._cfg.setSettings(Config.DEFAULT_DB_MAX_DAYS, int(self.spinDBMaxDays.value()))
+        self._cfg.setSettings(Config.DEFAULT_DB_PURGE_INTERVAL, int(self.spinDBPurgeInterval.value()))
+        self._cfg.setSettings(Config.DEFAULT_DB_JRNL_WAL, bool(self.checkDBJrnlWal.isChecked()))
+        self.dbType = self.comboDBType.currentIndex()
+
+        return True
+
+    def _save_ui_config(self):
+        try:
+            self._save_ui_columns_config()
+
+            maxmsgsize = self.comboGrpcMsgSize.currentText()
+            if maxmsgsize != "":
+                self._cfg.setSettings(Config.DEFAULT_SERVER_MAX_MESSAGE_LENGTH, maxmsgsize.replace(" ", ""))
+
+            savedauthtype = self._cfg.getSettings(Config.AUTH_TYPE)
+            authtype = self.comboAuthType.itemData(self.comboAuthType.currentIndex())
+            cacert = self._cfg.getSettings(Config.AUTH_CA_CERT)
+            cert = self._cfg.getSettings(Config.AUTH_CERT)
+            certkey = self._cfg.getSettings(Config.AUTH_CERTKEY)
+            if not self._validate_certs():
+                return
+
+            if savedauthtype != authtype or self.lineCertFile.text() != cert or \
+                    self.lineCertKeyFile.text() != certkey or self.lineCACertFile.text() != cacert:
+                self._changes_needs_restart = QC.translate("preferences", "Certificates changed")
+            self._cfg.setSettings(Config.AUTH_TYPE, authtype)
+            self._cfg.setSettings(Config.AUTH_CA_CERT, self.lineCACertFile.text())
+            self._cfg.setSettings(Config.AUTH_CERT, self.lineCertFile.text())
+            self._cfg.setSettings(Config.AUTH_CERTKEY, self.lineCertKeyFile.text())
+
+            selected_lang = self.comboUILang.itemData(self.comboUILang.currentIndex())
+            saved_lang = self._cfg.getSettings(Config.DEFAULT_LANGUAGE)
+            saved_lang = "" if saved_lang is None else saved_lang
+            if saved_lang != selected_lang:
+                languages.save(self._cfg, selected_lang)
+                self._changes_needs_restart = QC.translate("preferences", "Language changed")
+
+            self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES, int(self.comboUIRules.currentIndex()))
+            self._cfg.setSettings(self._cfg.DEFAULT_IGNORE_RULES, bool(self.checkUIRules.isChecked()))
+            #self._set_rules_duration_filter()
+            self._cfg.setRulesDurationFilter(
+                bool(self.checkUIRules.isChecked()),
+                int(self.comboUIRules.currentIndex())
+            )
+            if self.checkUIRules.isChecked():
+                self._nodes.delete_rule_by_field(Config.DURATION_FIELD, Config.RULES_DURATION_FILTER)
+
+            self._cfg.setSettings(self._cfg.STATS_REFRESH_INTERVAL, int(self.spinUIRefresh.value()))
+            self._cfg.setSettings(self._cfg.DEFAULT_ACTION_KEY, self.comboUIAction.currentIndex())
+            self._cfg.setSettings(self._cfg.DEFAULT_DURATION_KEY, int(self.comboUIDuration.currentIndex()))
+            self._cfg.setSettings(self._cfg.DEFAULT_TARGET_KEY, self.comboUITarget.currentIndex())
+            self._cfg.setSettings(self._cfg.DEFAULT_TIMEOUT_KEY, self.spinUITimeout.value())
+            self._cfg.setSettings(self._cfg.DEFAULT_DISABLE_POPUPS, bool(self.popupsCheck.isChecked()))
+            self._cfg.setSettings(self._cfg.DEFAULT_POPUP_POSITION, int(self.comboUIDialogPos.currentIndex()))
+
+            self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED, bool(self.showAdvancedCheck.isChecked()))
+            self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP, bool(self.dstIPCheck.isChecked()))
+            self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT, bool(self.dstPortCheck.isChecked()))
+            self._cfg.setSettings(self._cfg.DEFAULT_POPUP_ADVANCED_UID, bool(self.uidCheck.isChecked()))
+
+            self._cfg.setSettings(self._cfg.NOTIFICATIONS_ENABLED, bool(self.groupNotifs.isChecked()))
+            self._cfg.setSettings(self._cfg.NOTIFICATIONS_TYPE,
+                                int(Config.NOTIFICATION_TYPE_SYSTEM if self.radioSysNotifs.isChecked() else Config.NOTIFICATION_TYPE_QT))
+
+            self._themes.save_theme(self.comboUITheme.currentIndex(), self.comboUITheme.currentText(), str(self.spinUIDensity.value()))
+
+            qt_platform = self._cfg.getSettings(Config.QT_PLATFORM_PLUGIN)
+            if qt_platform != self.comboUIQtPlatform.currentText():
+                self._changes_needs_restart = QC.translate("preferences", "Qt platform plugin changed")
+            self._cfg.setSettings(Config.QT_PLATFORM_PLUGIN, self.comboUIQtPlatform.currentText())
+
+            self._cfg.setSettings(Config.QT_AUTO_SCREEN_SCALE_FACTOR, bool(self.checkUIAutoScreen.isChecked()))
+            self._cfg.setSettings(Config.QT_SCREEN_SCALE_FACTOR, self.lineUIScreenFactor.text())
+
+            if self._themes.available() and self._saved_theme != "" and self.comboUITheme.currentText() == QC.translate("preferences", "System"):
+                self._changes_needs_restart = QC.translate("preferences", "UI theme changed")
+
+            # this is a workaround for not display pop-ups.
+            # see #79 for more information.
+            if self.popupsCheck.isChecked():
+                self._cfg.setSettings(self._cfg.DEFAULT_TIMEOUT_KEY, 0)
+
+
+            self._autostart.enable(self.checkAutostart.isChecked())
+
+        except Exception as e:
+            self._set_status_error(str(e))
+
+    def _save_ui_columns_config(self):
+        cols=list()
+        if self.checkHideTime.isChecked():
+            cols.append("0")
+        if self.checkHideNode.isChecked():
+            cols.append("1")
+        if self.checkHideAction.isChecked():
+            cols.append("2")
+        if self.checkHideSrcPort.isChecked():
+            cols.append("3")
+        if self.checkHideSrcIP.isChecked():
+            cols.append("4")
+        if self.checkHideDstIP.isChecked():
+            cols.append("5")
+        if self.checkHideDstHost.isChecked():
+            cols.append("6")
+        if self.checkHideDstPort.isChecked():
+            cols.append("7")
+        if self.checkHideProto.isChecked():
+            cols.append("8")
+        if self.checkHideUID.isChecked():
+            cols.append("9")
+        if self.checkHidePID.isChecked():
+            cols.append("10")
+        if self.checkHideProc.isChecked():
+            cols.append("11")
+        if self.checkHideCmdline.isChecked():
+            cols.append("12")
+        if self.checkHideRule.isChecked():
+            cols.append("13")
+
+        self._cfg.setSettings(Config.STATS_SHOW_COLUMNS, cols)
+
+    def _save_nodes_config(self):
+        addr = self.comboNodes.currentText()
+        if (self._node_needs_update or self.checkApplyToNodes.isChecked()) and addr != "":
+            try:
+                notif = ui_pb2.Notification(
+                        id=int(str(time.time()).replace(".", "")),
+                        type=ui_pb2.CHANGE_CONFIG,
+                        data="",
+                        rules=[])
+                if self.checkApplyToNodes.isChecked():
+                    for addr in self._nodes.get_nodes():
+                        error = self._save_node_config(notif, addr)
+                        if error != None:
+                            self._set_status_error(error)
+                            return
+                else:
+                    error = self._save_node_config(notif, addr)
+                    if error != None:
+                        self._set_status_error(error)
+                        return
+            except Exception as e:
+                print(self.LOG_TAG + "exception saving config: ", e)
+                self._set_status_error(QC.translate("preferences", "Exception saving config: {0}").format(str(e)))
+        elif addr == "":
+            self._set_status_message(QC.translate("preferences", "There're no nodes connected"))
+
+        self._node_needs_update = False
+
+    def _save_node_config(self, notifObject, addr):
+        try:
+            self._set_status_message(QC.translate("preferences", "Applying configuration on {0} ...").format(addr))
+            notifObject.data, error = self._load_node_config(addr)
+            if error != None:
+                return error
+
+            savedAddr = self._cfg.getSettings(Config.DEFAULT_SERVER_ADDR)
+            # exclude this message if there're more than one node connected
+            if self.comboNodes.count() == 1 and savedAddr != None and savedAddr != self.comboNodeAddress.currentText():
+                self._changes_needs_restart = QC.translate("preferences", "Ok")
+
+            self._cfg.setSettings(Config.DEFAULT_SERVER_ADDR, self.comboNodeAddress.currentText())
+
+            self._nodes.save_node_config(addr, notifObject.data)
+            nid = self._nodes.send_notification(addr, notifObject, self._notification_callback)
+            self._notifications_sent[nid] = notifObject
+
+        except Exception as e:
+            print(self.LOG_TAG + "exception saving node config on %s: " % addr, e)
+            self._set_status_error(QC.translate("preferences", "Exception saving node config {0}: {1}").format((addr, str(e))))
+            return addr + ": " + str(e)
+
+        return None
+
+    def _validate_certs(self):
+        try:
+            if self.comboAuthType.currentIndex() == PreferencesDialog.AUTH_SIMPLE:
+                return True
+
+            if self.comboAuthType.currentIndex() > 0 and (self.lineCertFile.text() == "" or self.lineCertKeyFile.text() == ""):
+                raise ValueError(QC.translate("preferences", "Certs fields cannot be empty."))
+
+            if oct(stat.S_IMODE(os.lstat(self.lineCertFile.text()).st_mode)) != "0o600":
+                self._set_status_message(
+                    QC.translate("preferences", "cert file has excessive permissions, it should have 0600")
+                )
+            if oct(stat.S_IMODE(os.lstat(self.lineCertFile.text()).st_mode)) != "0o600":
+                self._set_status_message(
+                    QC.translate("preferences", "cert key file has excessive permissions, it should have 0600")
+                )
+
+            if self.comboAuthType.currentIndex() == PreferencesDialog.AUTH_TLS_MUTUAL:
+                if oct(stat.S_IMODE(os.lstat(self.lineCACertFile.text()).st_mode)) != "0o600":
+                    self._set_status_message(
+                        QC.translate("preferences", "CA cert file has excessive permissions, it should have 0600")
+                    )
+
+            return True
+        except Exception as e:
+            self._changes_needs_restart = None
+            self._set_status_error("certs error: {0}".format(e))
+            return False
+
+    def _needs_restart(self):
+        if self._changes_needs_restart:
+            Message.ok(self._changes_needs_restart,
+                self._restart_msg,
+                QtWidgets.QMessageBox.Warning)
+            self._changes_needs_restart = None
+
+
+    def _show_ui_density_widgets(self, idx):
+        """show ui density widget only for qt-material themes:
+            https://github.com/UN-GCPDS/qt-material?tab=readme-ov-file#density-scale
+        """
+        hidden = idx == 0
+        self.labelUIDensity.setHidden(hidden)
+        self.spinUIDensity.setHidden(hidden)
+        self.cmdUIDensityUp.setHidden(hidden)
+        self.cmdUIDensityDown.setHidden(hidden)
+
+    def _show_ui_scalefactor_widgets(self, show=False):
+        self.labelUIScreenFactor.setHidden(show)
+        self.lineUIScreenFactor.setHidden(show)
+
+    def _hide_status_label(self):
+        self.statusLabel.hide()
+
+    def _show_status_label(self):
+        self.statusLabel.show()
+
+    def _set_status_error(self, msg):
+        self._show_status_label()
+        self.statusLabel.setStyleSheet('color: red')
+        self.statusLabel.setText(msg)
+
+    def _set_status_successful(self, msg):
+        self._show_status_label()
+        self.statusLabel.setStyleSheet('color: green')
+        self.statusLabel.setText(msg)
+
+    def _set_status_message(self, msg):
+        self._show_status_label()
+        self.statusLabel.setStyleSheet('color: darkorange')
+        self.statusLabel.setText(msg)
+
+    def _reset_status_message(self):
+        self.statusLabel.setText("")
+        self._hide_status_label()
+
+    def _enable_db_cleaner_options(self, enable, db_max_days):
+        self.checkDBMaxDays.setChecked(enable)
+        self.spinDBMaxDays.setEnabled(enable)
+        self.spinDBPurgeInterval.setEnabled(enable)
+        self.labelDBPurgeInterval.setEnabled(enable)
+        self.labelDBPurgeDays.setEnabled(enable)
+        self.labelDBPurgeMinutes.setEnabled(enable)
+        self.cmdDBMaxDaysUp.setEnabled(enable)
+        self.cmdDBMaxDaysDown.setEnabled(enable)
+        self.cmdDBPurgesUp.setEnabled(enable)
+        self.cmdDBPurgesDown.setEnabled(enable)
+
+    def _enable_db_jrnl_wal(self, enable, db_jrnl_wal):
+        self.checkDBJrnlWal.setChecked(db_jrnl_wal)
+        self.checkDBJrnlWal.setEnabled(enable)
+
+    def _change_theme(self):
+        extra_opts = {
+            'density_scale': str(self.spinUIDensity.value())
+        }
+        self._themes.change_theme(self, self.comboUITheme.currentText(), extra_opts)
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def _cb_notification_callback(self, reply):
+        #print(self.LOG_TAG, "Config notification received: ", reply.id, reply.code)
+        if reply.id in self._notifications_sent:
+            if reply.code == ui_pb2.OK:
+                self._set_status_successful(QC.translate("preferences", "Configuration applied."))
+            else:
+                self._set_status_error(QC.translate("preferences", "Error applying configuration: {0}").format(reply.data))
+
+            del self._notifications_sent[reply.id]
+
+    def _cb_line_certs_changed(self, text):
+        self._changes_needs_restart = QC.translate("preferences", "Certs changed")
+
+    def _cb_node_line_certs_changed(self, text):
+        self._changes_needs_restart = QC.translate("preferences", "Node certs changed")
+        self._node_needs_update = True
+
+    def _cb_file_db_clicked(self):
+        options = QtWidgets.QFileDialog.Options()
+        fileName, _ = QtWidgets.QFileDialog.getSaveFileName(self, "", "","All Files (*)", options=options)
+        if fileName:
+            self.dbLabel.setText(fileName)
+
+    def _cb_combo_uirules_changed(self, idx):
+        self._cfg.setRulesDurationFilter(
+            self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES),
+            idx
+            #self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES)
+        )
+
+
+    def _cb_db_type_changed(self):
+        isDBMem = self.comboDBType.currentIndex() == Database.DB_TYPE_MEMORY
+        self.dbFileButton.setVisible(not isDBMem)
+        self.dbLabel.setVisible(not isDBMem)
+        self.checkDBMaxDays.setChecked(self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST))
+        self.checkDBJrnlWal.setEnabled(not isDBMem)
+        self.checkDBJrnlWal.setChecked(False)
+
+    def _cb_accept_button_clicked(self):
+        self.accept()
+        if not self._settingsSaved:
+            self._save_settings()
+
+    def _cb_apply_button_clicked(self):
+        self._reset_status_message()
+        self._save_settings()
+
+    def _cb_cancel_button_clicked(self):
+        self.reject()
+
+    def _cb_help_button_clicked(self):
+        QuickHelp.show(
+            QC.translate("preferences",
+                         "Hover the mouse over the texts to display the help<br><br>Don't forget to visit the wiki: <a href=\"{0}\">{0}</a>"
+                         ).format(Config.HELP_URL)
+        )
+
+    def _cb_popups_check_toggled(self, checked):
+        self.spinUITimeout.setEnabled(not checked)
+        if not checked:
+            self.spinUITimeout.setValue(20)
+
+    def _cb_node_combo_changed(self, index):
+        self._load_node_settings()
+
+    def _cb_node_needs_update(self):
+        self._node_needs_update = True
+
+    def _cb_ui_check_rules_toggled(self, state):
+        self.comboUIRules.setEnabled(state)
+
+    def _cb_combo_themes_changed(self, index):
+        self._change_theme()
+        self._show_ui_density_widgets(index)
+
+    def _cb_spin_uidensity_changed(self, value):
+        self._change_theme()
+
+    def _cb_ui_check_auto_scale_toggled(self, checked):
+        self._changes_needs_restart = QC.translate("preferences", "Auto scale option changed")
+        self._show_ui_scalefactor_widgets(checked)
+
+    def _cb_ui_screen_factor_changed(self, text):
+        self._changes_needs_restart = QC.translate("preferences", "Screen factor option changed")
+
+    def _cb_combo_auth_type_changed(self, index):
+        curtype = self.comboAuthType.itemData(self.comboAuthType.currentIndex())
+        savedtype = self._cfg.getSettings(Config.AUTH_TYPE)
+        if curtype != savedtype:
+            self._changes_needs_restart = QC.translate("preferences", "Auth type changed")
+
+        self.lineCACertFile.setEnabled(index == PreferencesDialog.AUTH_TLS_MUTUAL)
+        self.lineCertFile.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+        self.lineCertKeyFile.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+
+    def _cb_combo_node_auth_type_changed(self, index):
+        curtype = self.comboNodeAuthType.itemData(self.comboNodeAuthType.currentIndex())
+        #savedtype = self._cfg.getSettings(Config.AUTH_TYPE)
+        #if curtype != savedtype:
+        #    self._changes_needs_restart = QC.translate("preferences", "Auth type changed")
+
+        self.lineNodeCACertFile.setEnabled(index == PreferencesDialog.AUTH_TLS_MUTUAL)
+        self.lineNodeServerCertFile.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+        self.lineNodeCertFile.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+        self.lineNodeCertKeyFile.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+        self.checkNodeAuthSkipVerify.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+        self.comboNodeAuthVerifyType.setEnabled(index >= PreferencesDialog.AUTH_TLS_SIMPLE)
+
+        self._node_needs_update = True
+
+    def _cb_db_max_days_toggled(self, state):
+        self._enable_db_cleaner_options(state, 1)
+
+    def _cb_db_jrnl_wal_toggled(self, state):
+        self._changes_needs_restart = QC.translate("preferences", "DB journal_mode changed")
+
+    def _cb_cmd_spin_clicked(self, spinWidget, operation):
+        if operation == self.SUM:
+            spinWidget.setValue(spinWidget.value() + 1)
+        else:
+            spinWidget.setValue(spinWidget.value() - 1)
+
+        if spinWidget == self.popupsCheck:
+            enablePopups = spinWidget.value() > 0
+            self.popupsCheck.setChecked(not enablePopups)
+            self.spinUITimeout.setEnabled(enablePopups)
+
+    def _cb_radio_system_notifications(self):
+        if self._desktop_notifications.is_available() == False:
+            self.radioSysNotifs.setChecked(False)
+            self.radioQtNotifs.setChecked(True)
+            self._set_status_error(QC.translate("notifications", "System notifications are not available, you need to install python3-notify2."))
+            return
+
+    def _cb_test_notifs_clicked(self):
+        try:
+            self.cmdTestNotifs.setEnabled(False)
+            if self._desktop_notifications.is_available() == False:
+                self._set_status_error(QC.translate("notifications", "System notifications are not available, you need to install python3-notify2."))
+                return
+
+            if self.radioSysNotifs.isChecked():
+                self._desktop_notifications.show("title", "body")
+            else:
+                pass
+        except Exception as e:
+            print(self.LOG_TAG + "exception testing notifications:", e)
+        finally:
+            self.cmdTestNotifs.setEnabled(True)
diff --git a/ui/opensnitch/dialogs/processdetails.py b/ui/opensnitch/dialogs/processdetails.py
new file mode 100644 (file)
index 0000000..7c4aa20
--- /dev/null
@@ -0,0 +1,334 @@
+import os
+import sys
+import json
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from opensnitch.nodes import Nodes
+from opensnitch.desktop_parser import LinuxDesktopParser
+from opensnitch.utils import Message, Icons
+
+DIALOG_UI_PATH = "%s/../res/process_details.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class ProcessDetailsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+
+    LOG_TAG = "[ProcessDetails]: "
+
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+
+    TAB_STATUS          = 0
+    TAB_DESCRIPTORS     = 1
+    TAB_IOSTATS         = 2
+    TAB_MAPS            = 3
+    TAB_STACK           = 4
+    TAB_ENVS            = 5
+
+    TABS = {
+            TAB_STATUS: {
+                "text": None,
+                "scrollPos": 0
+                },
+            TAB_DESCRIPTORS: {
+                "text": None,
+                "scrollPos": 0
+                },
+            TAB_IOSTATS: {
+                "text": None,
+                "scrollPos": 0
+                },
+            TAB_MAPS: {
+                "text": None,
+                "scrollPos": 0
+                },
+            TAB_STACK: {
+                "text": None,
+                "scrollPos": 0
+                },
+            TAB_ENVS: {
+                "text": None,
+                "scrollPos": 0
+                }
+            }
+
+    def __init__(self, parent=None, appicon=None):
+        super(ProcessDetailsDialog, self).__init__(parent)
+        QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
+        self.setWindowFlags(QtCore.Qt.Window)
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+
+        self._app_name = None
+        self._app_icon = None
+        self._apps_parser = LinuxDesktopParser()
+        self._nodes = Nodes.instance()
+        self._notification_callback.connect(self._cb_notification_callback)
+
+        self._nid = None
+        self._pid = ""
+        self._notifications_sent = {}
+
+        self.cmdClose.clicked.connect(self._cb_close_clicked)
+        self.cmdAction.clicked.connect(self._cb_action_clicked)
+        self.comboPids.currentIndexChanged.connect(self._cb_combo_pids_changed)
+
+        self.TABS[self.TAB_STATUS]['text'] = self.textStatus
+        self.TABS[self.TAB_DESCRIPTORS]['text'] = self.textOpenedFiles
+        self.TABS[self.TAB_IOSTATS]['text'] = self.textIOStats
+        self.TABS[self.TAB_MAPS]['text'] = self.textMappedFiles
+        self.TABS[self.TAB_STACK]['text'] = self.textStack
+        self.TABS[self.TAB_ENVS]['text'] = self.textEnv
+
+        self.TABS[self.TAB_DESCRIPTORS]['text'].setFont(QtGui.QFont("monospace"))
+
+        self.iconStart = QtGui.QIcon.fromTheme("media-playback-start")
+        self.iconPause = QtGui.QIcon.fromTheme("media-playback-pause")
+
+        if QtGui.QIcon.hasThemeIcon("window-close"):
+            return
+
+        closeIcon = Icons.new(self, "window-close")
+        self.cmdClose.setIcon(closeIcon)
+        self.iconStart = Icons.new(self, "media-playback-start")
+        self.iconPause = Icons.new(self, "media-playback-pause")
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def _cb_notification_callback(self, reply):
+        if reply.id in self._notifications_sent:
+            noti = self._notifications_sent[reply.id]
+
+            if reply.code == ui_pb2.ERROR:
+                self._show_message(QtCore.QCoreApplication.translate("proc_details", "<b>Error loading process information:</b> <br><br>\n\n") + reply.data)
+                self._pid = ""
+                self._set_button_running(False)
+
+                # if we haven't loaded any data yet, just close the window
+                if self._data_loaded == False:
+                    # but if there're more than 1 pid keep the window open.
+                    # we may have one pid already closed and one alive.
+                    if self.comboPids.count() <= 1:
+                        self._close()
+
+                self._delete_notification(reply.id)
+                return
+
+            if noti.type == ui_pb2.MONITOR_PROCESS and reply.data != "":
+                self._load_data(reply.data)
+
+            elif noti.type == ui_pb2.STOP_MONITOR_PROCESS:
+                if reply.data != "":
+                    self._show_message(QtCore.QCoreApplication.translate("proc_details", "<b>Error stopping monitoring process:</b><br><br>") + reply.data)
+                    self._set_button_running(False)
+
+                self._delete_notification(reply.id)
+        else:
+            print("[stats] unknown notification received: ", reply.id)
+
+    def closeEvent(self, e):
+        self._close()
+
+    def _cb_close_clicked(self):
+        self._close()
+
+    def _cb_combo_pids_changed(self, idx):
+        if idx == -1:
+            return
+        # TODO: this event causes to send to 2 Start notifications
+        #if self._pid != "" and self._pid != self.comboPids.currentText():
+        #    self._stop_monitoring()
+        #    self._pid = self.comboPids.currentText()
+        #    self._start_monitoring()
+
+    def _cb_action_clicked(self):
+        if not self.cmdAction.isChecked():
+            self._stop_monitoring()
+        else:
+            self._start_monitoring()
+
+    def _show_message(self, text):
+        Message.ok(text, "", QtWidgets.QMessageBox.Warning)
+
+    def _delete_notification(self, nid):
+        if nid in self._notifications_sent:
+            del self._notifications_sent[nid]
+
+    def _reset(self):
+        self._app_name = None
+        self._app_icon = None
+        self.comboPids.clear()
+        self.labelProcName.setText(QtCore.QCoreApplication.translate("proc_details", "loading..."))
+        self.labelProcArgs.setText(QtCore.QCoreApplication.translate("proc_details", "loading..."))
+        self.labelProcIcon.clear()
+        self.labelStatm.setText("")
+        self.labelCwd.setText("")
+        for tidx in range(0, len(self.TABS)):
+            self.TABS[tidx]['text'].setPlainText("")
+
+    def _set_button_running(self, yes):
+        if yes:
+            self.cmdAction.setChecked(True)
+            self.cmdAction.setIcon(self.iconPause)
+        else:
+            self.cmdAction.setChecked(False)
+            self.cmdAction.setIcon(self.iconStart)
+
+    def _close(self):
+        self._stop_monitoring()
+        self.comboPids.clear()
+        self._pid = ""
+        self.hide()
+
+    def monitor(self, pids):
+        if self._pid != "":
+            self._stop_monitoring()
+
+        self._data_loaded = False
+        self._pids = pids
+        self._reset()
+        for pid in pids:
+            if pid != None:
+                self.comboPids.addItem(pid)
+
+        self.show()
+        self._start_monitoring()
+
+    def _set_tab_text(self, tab_idx, text):
+        self.TABS[tab_idx]['scrollPos'] = self.TABS[tab_idx]['text'].verticalScrollBar().value()
+        self.TABS[tab_idx]['text'].setPlainText(text)
+        self.TABS[tab_idx]['text'].verticalScrollBar().setValue(self.TABS[tab_idx]['scrollPos'])
+
+    def _start_monitoring(self):
+        try:
+            # avoid to send notifications without a pid
+            if self._pid != "":
+                return
+
+            self._pid = self.comboPids.currentText()
+            if self._pid == "":
+                return
+
+            self._set_button_running(True)
+            noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.MONITOR_PROCESS, data=self._pid, rules=[])
+            self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback)
+            self._notifications_sent[self._nid] = noti
+        except Exception as e:
+            print(self.LOG_TAG + "exception starting monitoring: ", e)
+
+    def _stop_monitoring(self):
+        if self._pid == "":
+            return
+
+        self._set_button_running(False)
+        noti = ui_pb2.Notification(clientName="", serverName="", type=ui_pb2.STOP_MONITOR_PROCESS, data=str(self._pid), rules=[])
+        self._nid = self._nodes.send_notification(self._pids[self._pid], noti, self._notification_callback)
+        self._notifications_sent[self._nid] = noti
+        self._pid = ""
+        self._app_icon = None
+
+    def _load_data(self, data):
+        tab_idx = self.tabWidget.currentIndex()
+
+        try:
+            proc = json.loads(data)
+            self._load_app_icon(proc['Path'])
+            if self._app_name != None:
+                self.labelProcName.setText("<b>" + self._app_name + "</b>")
+                self.labelProcName.setToolTip("<b>" + self._app_name + "</b>")
+
+            #if proc['Path'] not in proc['Args']:
+            #    proc['Args'].insert(0, proc['Path'])
+
+            self.labelProcArgs.setFixedHeight(30)
+            self.labelProcArgs.setText(" ".join(proc['Args']))
+            self.labelProcArgs.setToolTip(" ".join(proc['Args']))
+            self.labelCwd.setText("<b>CWD: </b>" + proc['CWD'])
+            self.labelCwd.setToolTip("<b>CWD: </b>" + proc['CWD'])
+            self._load_mem_data(proc['Statm'])
+
+            if tab_idx == self.TAB_STATUS:
+                self._set_tab_text(tab_idx, proc['Status'])
+
+            elif tab_idx == self.TAB_DESCRIPTORS:
+                self._load_descriptors(proc['Descriptors'])
+
+            elif tab_idx == self.TAB_IOSTATS:
+                self._load_iostats(proc['IOStats'])
+
+            elif tab_idx == self.TAB_MAPS:
+                self._set_tab_text(tab_idx, proc['Maps'])
+
+            elif tab_idx == self.TAB_STACK:
+                self._set_tab_text(tab_idx, proc['Stack'])
+
+            elif tab_idx == self.TAB_ENVS:
+                self._load_env_vars(proc['Env'])
+
+            self._data_loaded = True
+
+        except Exception as e:
+            print(self.LOG_TAG + "exception loading data: ", e)
+
+    def _load_app_icon(self, proc_path):
+        if self._app_icon != None:
+            return
+
+        self._app_name, self._app_icon, _, _ = self._apps_parser.get_info_by_path(proc_path, "terminal")
+
+        icon = QtGui.QIcon().fromTheme(self._app_icon)
+        pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
+        self.labelProcIcon.setPixmap(pixmap)
+
+        if self._app_name == None:
+            self._app_name = proc_path
+
+    def _load_iostats(self, iostats):
+        ioText = "%-16s %dMB<br>%-16s %dMB<br>%-16s %d<br>%-16s %d<br>%-16s %dMB<br>%-16s %dMB<br>" % (
+                "<b>Chars read:</b>",
+                ((iostats['RChar'] / 1024) / 1024),
+                "<b>Chars written:</b>",
+                ((iostats['WChar'] / 1024) / 1024),
+                "<b>Syscalls read:</b>",
+                (iostats['SyscallRead']),
+                "<b>Syscalls write:</b>",
+                (iostats['SyscallWrite']),
+                "<b>KB read:</b>",
+                ((iostats['ReadBytes'] / 1024) / 1024),
+                "<b>KB written: </b>",
+                ((iostats['WriteBytes'] / 1024) / 1024)
+                )
+
+        self.textIOStats.setPlainText("")
+        self.textIOStats.appendHtml(ioText)
+
+    def _load_mem_data(self, mem):
+        # assuming page size == 4096
+        pagesize = 4096
+        memText = "<b>VIRT:</b> %dMB, <b>RSS:</b> %dMB, <b>Libs:</b> %dMB, <b>Data:</b> %dMB, <b>Text:</b> %dMB" % (
+                ((mem['Size'] * pagesize) / 1024) / 1024,
+                ((mem['Resident'] * pagesize) / 1024) / 1024,
+                ((mem['Lib'] * pagesize) / 1024) / 1024,
+                ((mem['Data'] * pagesize) / 1024) / 1024,
+                ((mem['Text'] * pagesize) / 1024) / 1024
+                )
+        self.labelStatm.setText(memText)
+
+    def _load_descriptors(self, descriptors):
+        text = "%-12s%-40s%-8s -> %s\n\n" % ("Size", "Time", "Name", "Symlink")
+        for d in descriptors:
+            text += "{:<12}{:<40}{:<8} -> {}\n".format(str(d['Size']), d['ModTime'], d['Name'], d['SymLink'])
+
+        self._set_tab_text(self.TAB_DESCRIPTORS, text)
+
+    def _load_env_vars(self, envs):
+        if envs == {}:
+            self._set_tab_text(self.TAB_ENVS, "<no environment variables>")
+            return
+
+        text = "%-15s\t%s\n\n" % ("Name", "Value")
+        for env_name in envs:
+            text += "%-15s:\t%s\n" % (env_name, envs[env_name])
+
+        self._set_tab_text(self.TAB_ENVS, text)
+
+
diff --git a/ui/opensnitch/dialogs/prompt.py b/ui/opensnitch/dialogs/prompt.py
new file mode 100644 (file)
index 0000000..2adf46c
--- /dev/null
@@ -0,0 +1,775 @@
+import threading
+import sys
+import time
+import os
+import os.path
+import pwd
+import json
+import ipaddress
+from datetime import datetime
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QCoreApplication as QC, QEvent
+
+from slugify import slugify
+
+from opensnitch.utils import Icons
+from opensnitch.desktop_parser import LinuxDesktopParser
+from opensnitch.config import Config
+from opensnitch.version import version
+from opensnitch.actions import Actions
+from opensnitch.rules import Rules, Rule
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+DIALOG_UI_PATH = "%s/../res/prompt.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class PromptDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+    _prompt_trigger = QtCore.pyqtSignal()
+    _tick_trigger = QtCore.pyqtSignal()
+    _timeout_trigger = QtCore.pyqtSignal()
+
+    DEFAULT_TIMEOUT = 15
+
+    # don't translate
+    FIELD_REGEX_HOST    = "regex_host"
+    FIELD_REGEX_IP      = "regex_ip"
+    FIELD_PROC_PATH     = "process_path"
+    FIELD_PROC_ARGS     = "process_args"
+    FIELD_PROC_ID       = "process_id"
+    FIELD_USER_ID       = "user_id"
+    FIELD_DST_IP        = "dst_ip"
+    FIELD_DST_PORT      = "dst_port"
+    FIELD_DST_NETWORK   = "dst_network"
+    FIELD_DST_HOST      = "simple_host"
+    FIELD_APPIMAGE      = "appimage_path"
+
+    DURATION_30s    = "30s"
+    DURATION_5m     = "5m"
+    DURATION_15m    = "15m"
+    DURATION_30m    = "30m"
+    DURATION_1h     = "1h"
+    # don't translate
+
+    APPIMAGE_PREFIX = "/tmp/.mount_"
+
+    # label displayed in the pop-up combo
+    DURATION_session = QC.translate("popups", "until reboot")
+    # label displayed in the pop-up combo
+    DURATION_forever = QC.translate("popups", "forever")
+
+    def __init__(self, parent=None, appicon=None):
+        QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.WindowStaysOnTopHint)
+        # Other interesting flags: QtCore.Qt.Tool | QtCore.Qt.BypassWindowManagerHint
+        self._cfg = Config.get()
+        self._rules = Rules.instance()
+
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+        self.installEventFilter(self)
+
+        self._width = self.width()
+        self._height = self.height()
+        self.reset_widgets()
+
+        dialog_geometry = self._cfg.getSettings("promptDialog/geometry")
+        if dialog_geometry == QtCore.QByteArray:
+            self.restoreGeometry(dialog_geometry)
+
+        self.setWindowTitle("OpenSnitch v%s" % version)
+
+        self._lock = threading.Lock()
+        self._con = None
+        self._rule = None
+        self._local = True
+        self._peer = None
+        self._prompt_trigger.connect(self.on_connection_prompt_triggered)
+        self._timeout_trigger.connect(self.on_timeout_triggered)
+        self._tick_trigger.connect(self.on_tick_triggered)
+        self._tick = int(self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey(self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT
+        self._tick_thread = None
+        self._done = threading.Event()
+        self._timeout_text = ""
+        self._timeout_triggered = False
+
+        self._apps_parser = LinuxDesktopParser()
+
+        self.whatIPCombo.setVisible(False)
+        self.checkDstIP.setVisible(False)
+        self.checkDstPort.setVisible(False)
+        self.checkUserID.setVisible(False)
+        self.appDescriptionLabel.setVisible(False)
+
+        self._ischeckAdvanceded = False
+        self.checkAdvanced.toggled.connect(self._check_advanced_toggled)
+
+        self.checkAdvanced.clicked.connect(self._button_clicked)
+        self.durationCombo.activated.connect(self._button_clicked)
+        self.whatCombo.activated.connect(self._button_clicked)
+        self.whatIPCombo.activated.connect(self._button_clicked)
+        self.checkDstIP.clicked.connect(self._button_clicked)
+        self.checkDstPort.clicked.connect(self._button_clicked)
+        self.checkUserID.clicked.connect(self._button_clicked)
+
+        self.allowIcon = Icons.new(self, "emblem-default")
+        denyIcon = Icons.new(self, "emblem-important")
+        rejectIcon = Icons.new(self, "window-close")
+
+        self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+
+        self.allowButton.clicked.connect(lambda: self._on_action_clicked(Config.ACTION_ALLOW_IDX))
+        self.allowButton.setIcon(self.allowIcon)
+        self._allow_text = QC.translate("popups", "Allow")
+        self._action_text = [
+            QC.translate("popups", "Deny"),
+            QC.translate("popups", "Allow"),
+            QC.translate("popups", "Reject")
+        ]
+        self._action_icon = [denyIcon, self.allowIcon, rejectIcon]
+
+        m = QtWidgets.QMenu()
+        m.addAction(denyIcon, self._action_text[Config.ACTION_DENY_IDX]).triggered.connect(
+            lambda: self._on_action_clicked(Config.ACTION_DENY_IDX)
+        )
+        m.addAction(self.allowIcon, self._action_text[Config.ACTION_ALLOW_IDX]).triggered.connect(
+            lambda: self._on_action_clicked(Config.ACTION_ALLOW_IDX)
+        )
+        m.addAction(rejectIcon, self._action_text[Config.ACTION_REJECT_IDX]).triggered.connect(
+            lambda: self._on_action_clicked(Config.ACTION_REJECT_IDX)
+        )
+        self.actionButton.setMenu(m)
+        self.actionButton.setText(self._action_text[Config.ACTION_DENY_IDX])
+        self.actionButton.setIcon(self._action_icon[Config.ACTION_DENY_IDX])
+        if self._default_action != Config.ACTION_ALLOW_IDX:
+            self.actionButton.setText(self._action_text[self._default_action])
+            self.actionButton.setIcon(self._action_icon[self._default_action])
+        self.actionButton.clicked.connect(self._on_deny_btn_clicked)
+
+    def eventFilter(self, obj, event):
+        if event.type() == QEvent.MouseButtonPress:
+            self._stop_countdown()
+            return True
+        return False
+
+    def showEvent(self, event):
+        super(PromptDialog, self).showEvent(event)
+        self.activateWindow()
+        self.adjust_size()
+        self.move_popup()
+
+    def reset_widgets(self):
+        # Don't allow labels to grow more than the dialog's width.
+        # This can happen if the path or the binary name is too large.
+
+        self.appNameLabel.setMaximumWidth(self._width-5)
+        self.appDescriptionLabel.setMaximumWidth(self._width-5)
+        self.appPathLabel.setMaximumWidth(self._width-5)
+        self.argsLabel.setMaximumWidth(self._width-5)
+        self.messageLabel.setMaximumWidth(self._width-5)
+
+        self.appNameLabel.setText("")
+        self.appDescriptionLabel.setText("")
+        self.appPathLabel.setText("")
+        self.argsLabel.setText("")
+        self.messageLabel.setText("")
+
+    def adjust_size(self):
+        if self._width is None or self._height is None:
+            self._width = self.width()
+            self._height = self.height()
+
+        self.resize(QtCore.QSize(self._width, self._height))
+
+    def move_popup(self):
+        popup_pos = self._cfg.getInt(self._cfg.DEFAULT_POPUP_POSITION)
+        point = QtWidgets.QDesktopWidget().availableGeometry()
+        if popup_pos == self._cfg.POPUP_TOP_RIGHT:
+            self.move(point.topRight())
+        elif popup_pos == self._cfg.POPUP_TOP_LEFT:
+            self.move(point.topLeft())
+        elif popup_pos == self._cfg.POPUP_BOTTOM_RIGHT:
+            self.move(point.bottomRight())
+        elif popup_pos == self._cfg.POPUP_BOTTOM_LEFT:
+            self.move(point.bottomLeft())
+
+    def _stop_countdown(self):
+        action_idx = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+        if action_idx == Config.ACTION_ALLOW_IDX:
+            self.allowButton.setText(self._allow_text)
+            self.allowButton.setIcon(self.allowIcon)
+        else:
+            self.actionButton.setText(self._action_text[action_idx])
+            self.actionButton.setIcon(self._action_icon[action_idx])
+        self._tick_thread.stop = True
+
+    def _check_advanced_toggled(self, state):
+        self.checkDstIP.setVisible(state)
+        self.whatIPCombo.setVisible(state)
+        self.destIPLabel.setVisible(not state)
+        self.checkDstPort.setVisible(state == True and (self._con != None and self._con.dst_port != 0))
+        self.checkUserID.setVisible(state)
+
+        self._ischeckAdvanceded = state
+        self.adjust_size()
+        self.move_popup()
+
+    def _button_clicked(self):
+        self._stop_countdown()
+
+    def truncate_text(self, text, max_size=64):
+        if len(text) > max_size:
+            text = text[:max_size] + "..."
+        return text
+
+    def _set_elide_text(self, widget, text, max_size=64):
+        text = self.truncate_text(text, max_size)
+        widget.setText(text)
+
+    def promptUser(self, connection, is_local, peer):
+        # one at a time
+        with self._lock:
+            # reset state
+            self.reset_widgets()
+            if self._tick_thread != None and self._tick_thread.is_alive():
+                self._tick_thread.join()
+
+            self._cfg.reload()
+            self._tick = int(self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY)) if self._cfg.hasKey(self._cfg.DEFAULT_TIMEOUT_KEY) else self.DEFAULT_TIMEOUT
+            self._tick_thread = threading.Thread(target=self._timeout_worker)
+            self._tick_thread.stop = self._ischeckAdvanceded
+            self._timeout_triggered = False
+            self._rule = None
+            self._local = is_local
+            self._peer = peer
+            self._con = connection
+            self._done.clear()
+            # trigger and show dialog
+            self._prompt_trigger.emit()
+            # start timeout thread
+            self._tick_thread.start()
+            # wait for user choice or timeout
+            self._done.wait()
+
+            return self._rule, self._timeout_triggered
+
+    def _timeout_worker(self):
+        if self._tick == 0:
+            self._timeout_trigger.emit()
+            return
+
+        while self._tick > 0 and self._done.is_set() is False:
+            t = threading.currentThread()
+            # stop only stops the coundtdown, not the thread itself.
+            if getattr(t, "stop", True):
+                self._tick = int(self._cfg.getSettings(self._cfg.DEFAULT_TIMEOUT_KEY))
+                time.sleep(1)
+                continue
+
+            self._tick -= 1
+            self._tick_trigger.emit()
+            time.sleep(1)
+
+        if not self._done.is_set():
+            self._timeout_trigger.emit()
+
+    @QtCore.pyqtSlot()
+    def on_connection_prompt_triggered(self):
+        self._render_connection(self._con)
+        if self._tick > 0:
+            self.show()
+
+    @QtCore.pyqtSlot()
+    def on_tick_triggered(self):
+        self._set_cmd_action_text()
+
+    @QtCore.pyqtSlot()
+    def on_timeout_triggered(self):
+        self._timeout_triggered = True
+        self._send_rule()
+
+    def _hide_widget(self, widget, hide):
+        widget.setVisible(not hide)
+
+    def _configure_default_duration(self):
+        if self._cfg.hasKey(self._cfg.DEFAULT_DURATION_KEY):
+            cur_idx = self._cfg.getInt(self._cfg.DEFAULT_DURATION_KEY)
+            self.durationCombo.setCurrentIndex(cur_idx)
+        else:
+            self.durationCombo.setCurrentIndex(self._cfg.DEFAULT_DURATION_IDX)
+
+    def _set_cmd_action_text(self):
+        action_idx = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+        if action_idx == Config.ACTION_ALLOW_IDX:
+            self.allowButton.setText("{0} ({1})".format(self._allow_text, self._tick))
+            self.allowButton.setIcon(self.allowIcon)
+            self.actionButton.setText(self._action_text[Config.ACTION_DENY_IDX])
+        else:
+            self.allowButton.setText(self._allow_text)
+            self.actionButton.setText("{0} ({1})".format(self._action_text[action_idx], self._tick))
+            self.actionButton.setIcon(self._action_icon[action_idx])
+
+    def _set_app_description(self, description):
+        if description != None and description != "":
+            self.appDescriptionLabel.setVisible(True)
+            self.appDescriptionLabel.setFixedHeight(50)
+            self.appDescriptionLabel.setToolTip(description)
+            self._set_elide_text(self.appDescriptionLabel, "%s" % description)
+        else:
+            self.appDescriptionLabel.setVisible(False)
+            self.appDescriptionLabel.setFixedHeight(0)
+            self.appDescriptionLabel.setText("")
+            return
+
+        self.appDescriptionLabel.setText(
+            "".join(
+                filter(str.isprintable, self.appDescriptionLabel.text())
+            )
+        )
+
+    def _set_app_path(self, app_name, app_args, con):
+        # show the binary path if it's not part of the cmdline args:
+        # cmdline: telnet 1.1.1.1 (path: /usr/bin/telnet.netkit)
+        # cmdline: /usr/bin/telnet.netkit 1.1.1.1 (the binary path is part of the cmdline args, no need to display it)
+        if con.process_path != "" and len(con.process_args) >= 1 and con.process_path not in con.process_args:
+            self.appPathLabel.setToolTip("Process path: %s" % con.process_path)
+            if app_name.lower() == app_args:
+                self._set_elide_text(self.appPathLabel, "%s" % con.process_path)
+            else:
+                self._set_elide_text(self.appPathLabel, "(%s)" % con.process_path)
+            self.appPathLabel.setVisible(True)
+        elif con.process_path != "" and len(con.process_args) == 0:
+            self._set_elide_text(self.appPathLabel, "%s" % con.process_path)
+            self.appPathLabel.setVisible(True)
+        else:
+            self.appPathLabel.setVisible(False)
+            self.appPathLabel.setText("")
+            return
+
+        self.appPathLabel.setText(
+            "".join(
+                filter(str.isprintable, self.appPathLabel.text())
+            )
+        )
+        if self.appPathLabel.width() >= self._width:
+            self.appPathLabel.setText("\u200b".join(self.appPathLabel.text()))
+
+    def _set_app_args(self, app_name, app_args):
+        # if the app name and the args are the same, there's no need to display
+        # the args label (amule for example)
+        if app_name.lower() != app_args:
+            self.argsLabel.setVisible(True)
+            self._set_elide_text(self.argsLabel, app_args, 256)
+            self.argsLabel.setToolTip(app_args)
+        else:
+            self.argsLabel.setVisible(False)
+            self.argsLabel.setText("")
+            return
+
+        self.argsLabel.setText(
+            "".join(
+                filter(str.isprintable, self.argsLabel.text())
+            )
+        )
+        if self.argsLabel.width() >= self._width:
+            self.argsLabel.setText("\u200b".join(self.argsLabel.text()))
+
+    def _set_default_target(self, combo, con, app_name, app_args):
+        # set appimage as default target if the process path starts with
+        # /tmp/._mount
+        if con.process_path.startswith(self.APPIMAGE_PREFIX):
+            idx = combo.findData(self.FIELD_APPIMAGE)
+            if idx != -1:
+                combo.setCurrentIndex(idx)
+                return
+
+        if int(con.process_id) > 0 and app_name != "" and app_args != "":
+            self.whatCombo.setCurrentIndex(int(self._cfg.getSettings(self._cfg.DEFAULT_TARGET_KEY)))
+        else:
+            self.whatCombo.setCurrentIndex(2)
+
+    def _render_connection(self, con):
+        app_name, app_icon, description, _ = self._apps_parser.get_info_by_path(con.process_path, "terminal")
+        app_args = " ".join(con.process_args)
+        self._set_app_description(description)
+        self._set_app_path(app_name, app_args, con)
+        self._set_app_args(app_name, app_args)
+
+        if app_name == "":
+            self.appPathLabel.setVisible(False)
+            self.argsLabel.setVisible(False)
+            app_name = QC.translate("popups", "Unknown process %s" % con.process_path)
+            self.appNameLabel.setText(QC.translate("popups", "Outgoing connection"))
+        else:
+            self._set_elide_text(self.appNameLabel, "%s" % app_name, max_size=42)
+            self.appNameLabel.setToolTip(app_name)
+
+        self.cwdLabel.setToolTip("%s %s" % (QC.translate("popups", "Process launched from:"), con.process_cwd))
+        self._set_elide_text(self.cwdLabel, con.process_cwd, max_size=32)
+
+        pixmap = self._get_app_icon(app_icon)
+        self.iconLabel.setPixmap(pixmap)
+
+        message = self._get_popup_message(app_name, con)
+
+        self.messageLabel.setText(message)
+        self.messageLabel.setToolTip(message)
+
+        self.sourceIPLabel.setText(con.src_ip)
+        self.destIPLabel.setText(con.dst_ip)
+        if con.dst_port == 0:
+            self.destPortLabel.setText("")
+        else:
+            self.destPortLabel.setText(str(con.dst_port))
+        self._hide_widget(self.destPortLabel, con.dst_port == 0)
+        self._hide_widget(self.destPortLabel_1, con.dst_port == 0)
+        self._hide_widget(self.checkDstPort, con.dst_port == 0 or not self._ischeckAdvanceded)
+
+        if self._local:
+            try:
+                uid = "%d (%s)" % (con.user_id, pwd.getpwuid(con.user_id).pw_name)
+            except:
+                uid = ""
+        else:
+            uid = "%d" % con.user_id
+
+        self.uidLabel.setText(uid)
+        self.pidLabel.setText("%s" % con.process_id)
+
+        self.whatCombo.clear()
+        self.whatIPCombo.clear()
+
+        # the order of these combobox entries must match those in the preferences dialog
+        # prefs -> UI -> Default target
+        self.whatCombo.addItem(QC.translate("popups", "from this executable"), self.FIELD_PROC_PATH)
+        if int(con.process_id) < 0:
+            self.whatCombo.model().item(0).setEnabled(False)
+
+        self.whatCombo.addItem(QC.translate("popups", "from this command line"), self.FIELD_PROC_ARGS)
+
+        self.whatCombo.addItem(QC.translate("popups", "to port {0}").format(con.dst_port), self.FIELD_DST_PORT)
+        self.whatCombo.addItem(QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP)
+
+        self.whatCombo.addItem(QC.translate("popups", "from user {0}").format(uid), self.FIELD_USER_ID)
+        if int(con.user_id) < 0:
+            self.whatCombo.model().item(4).setEnabled(False)
+
+        self.whatCombo.addItem(QC.translate("popups", "from this PID"), self.FIELD_PROC_ID)
+        #######################
+
+        if con.process_path.startswith(self.APPIMAGE_PREFIX):
+            self._add_appimage_pattern_to_combo(self.whatCombo, con)
+
+        self._add_dst_networks_to_combo(self.whatCombo, con.dst_ip)
+
+        if con.dst_host != "" and con.dst_host != con.dst_ip:
+            self._add_dsthost_to_combo(con.dst_host)
+
+        self.whatIPCombo.addItem(QC.translate("popups", "to {0}").format(con.dst_ip), self.FIELD_DST_IP)
+
+        parts = con.dst_ip.split('.')
+        nparts = len(parts)
+        for i in range(1, nparts):
+            self.whatCombo.addItem(QC.translate("popups", "to {0}.*").format('.'.join(parts[:i])), self.FIELD_REGEX_IP)
+            self.whatIPCombo.addItem(QC.translate("popups", "to {0}.*").format( '.'.join(parts[:i])), self.FIELD_REGEX_IP)
+
+        self._add_dst_networks_to_combo(self.whatIPCombo, con.dst_ip)
+
+        self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+
+        self._configure_default_duration()
+
+        self._set_default_target(self.whatCombo, con, app_name, app_args)
+
+        self.checkDstIP.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTIP))
+        self.checkDstPort.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_DSTPORT))
+        self.checkUserID.setChecked(self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED_UID))
+        if self._cfg.getBool(self._cfg.DEFAULT_POPUP_ADVANCED):
+            self.checkAdvanced.toggle()
+
+        self._set_cmd_action_text()
+        self.checkAdvanced.setFocus()
+
+        self.setFixedSize(self.size())
+
+    # https://gis.stackexchange.com/questions/86398/how-to-disable-the-escape-key-for-a-dialog
+    def keyPressEvent(self, event):
+        if not event.key() == QtCore.Qt.Key_Escape:
+            super(PromptDialog, self).keyPressEvent(event)
+
+    # prevent a click on the window's x
+    # from quitting the whole application
+    def closeEvent(self, e):
+        self._send_rule()
+        e.ignore()
+
+    def _add_appimage_pattern_to_combo(self, combo, con):
+        """appimages' absolute path usually starts with /tmp/.mount_
+        """
+        appimage_bin = os.path.basename(con.process_path)
+        appimage_path = os.path.dirname(con.process_path)
+        appimage_path = appimage_path[0:len(self.APPIMAGE_PREFIX)+6]
+        combo.addItem(
+            QC.translate("popups", "from {0}*/{1}").format(appimage_path, appimage_bin),
+            self.FIELD_APPIMAGE
+        )
+
+    def _add_dst_networks_to_combo(self, combo, dst_ip):
+        if type(ipaddress.ip_address(dst_ip)) == ipaddress.IPv4Address:
+            combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/24", strict=False)),  self.FIELD_DST_NETWORK)
+            combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/16", strict=False)),  self.FIELD_DST_NETWORK)
+            combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/8", strict=False)),   self.FIELD_DST_NETWORK)
+        else:
+            combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/64", strict=False)),  self.FIELD_DST_NETWORK)
+            combo.addItem(QC.translate("popups", "to {0}").format(ipaddress.ip_network(dst_ip + "/128", strict=False)), self.FIELD_DST_NETWORK)
+
+    def _add_dsthost_to_combo(self, dst_host):
+        self.whatCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST)
+        self.whatIPCombo.addItem("%s" % dst_host, self.FIELD_DST_HOST)
+
+        parts = dst_host.split('.')[1:]
+        nparts = len(parts)
+        for i in range(0, nparts - 1):
+            self.whatCombo.addItem(QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST)
+            self.whatIPCombo.addItem(QC.translate("popups", "to *.{0}").format('.'.join(parts[i:])), self.FIELD_REGEX_HOST)
+
+
+    def _get_app_icon(self, app_icon):
+        """we try to get the icon of an app from the system.
+        If it's not found, then we'll try to search for it in common directories
+        of the system.
+        """
+        try:
+            icon = QtGui.QIcon().fromTheme(app_icon)
+            pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
+            if QtGui.QIcon().hasThemeIcon(app_icon) == False or pixmap.height() == 0:
+                # sometimes the icon is an absolute path, sometimes it's not
+                if os.path.isabs(app_icon):
+                    icon = QtGui.QIcon(app_icon)
+                    pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
+                else:
+                    icon_path = self._apps_parser.discover_app_icon(app_icon)
+                    if icon_path != None:
+                        icon = QtGui.QIcon(icon_path)
+                        pixmap = icon.pixmap(icon.actualSize(QtCore.QSize(48, 48)))
+        except Exception as e:
+            print("Exception _get_app_icon():", e)
+
+        return pixmap
+
+    def _get_popup_message(self, app_name, con):
+        """
+        _get_popup_message helps constructing the message that is displayed on
+        the pop-up dialog. Example:
+            curl is connecting to www.opensnitch.io on TCP port 443
+        """
+        app_name = self.truncate_text(app_name)
+        message = "<b>%s</b>" % app_name
+        if not self._local:
+            message = QC.translate("popups", "<b>Remote</b> process %s running on <b>%s</b>") % ( \
+                message,
+                self._peer.split(':')[1])
+
+        msg_action = QC.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \
+            con.dst_host or con.dst_ip,
+            con.protocol.upper(),
+            con.dst_port )
+
+        # icmp port is 0 (i.e.: no port)
+        if con.dst_port == 0:
+            msg_action = QC.translate("popups", "is connecting to <b>%s</b>, %s") % ( \
+                con.dst_host or con.dst_ip,
+                con.protocol.upper() )
+
+        if con.dst_port == 53 and con.dst_ip != con.dst_host and con.dst_host != "":
+            msg_action = QC.translate("popups", "is attempting to resolve <b>%s</b> via %s, %s port %d") % ( \
+                con.dst_host,
+                con.dst_ip,
+                con.protocol.upper(),
+                con.dst_port)
+
+        if self.messageLabel.width() >= self._width:
+            self.messageLabel.setText("\u200b".join(self.messageLabel.text()))
+
+        return "%s %s" % (message, msg_action)
+
+    def _get_duration(self, duration_idx):
+        if duration_idx == 0:
+            return Config.DURATION_ONCE
+        elif duration_idx == 1:
+            return self.DURATION_30s
+        elif duration_idx == 2:
+            return self.DURATION_5m
+        elif duration_idx == 3:
+            return self.DURATION_15m
+        elif duration_idx == 4:
+            return self.DURATION_30m
+        elif duration_idx == 5:
+            return self.DURATION_1h
+        elif duration_idx == 6:
+            return Config.DURATION_UNTIL_RESTART
+        else:
+            return Config.DURATION_ALWAYS
+
+    def _get_combo_operator(self, combo, what_idx, con):
+        if combo.itemData(what_idx) == self.FIELD_PROC_PATH:
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_PROCESS_PATH, con.process_path
+
+        elif combo.itemData(what_idx) == self.FIELD_PROC_ARGS:
+            # this should not happen
+            if len(con.process_args) == 0 or con.process_args[0] == "":
+                return Config.RULE_TYPE_SIMPLE, Config.OPERAND_PROCESS_PATH, con.process_path
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_PROCESS_COMMAND, ' '.join(con.process_args)
+
+        elif combo.itemData(what_idx) == self.FIELD_PROC_ID:
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_PROCESS_ID, "{0}".format(con.process_id)
+
+        elif combo.itemData(what_idx) == self.FIELD_USER_ID:
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_USER_ID, "%s" % con.user_id
+
+        elif combo.itemData(what_idx) == self.FIELD_DST_PORT:
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_DEST_PORT, "%s" % con.dst_port
+
+        elif combo.itemData(what_idx) == self.FIELD_DST_IP:
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_DEST_IP, con.dst_ip
+
+        elif combo.itemData(what_idx) == self.FIELD_DST_HOST:
+            return Config.RULE_TYPE_SIMPLE, Config.OPERAND_DEST_HOST, combo.currentText()
+
+        elif combo.itemData(what_idx) == self.FIELD_DST_NETWORK:
+            # strip "to ": "to x.x.x/20" -> "x.x.x/20"
+            # we assume that to is one word in all languages
+            parts = combo.currentText().split(' ')
+            text = parts[len(parts)-1]
+            return Config.RULE_TYPE_NETWORK, Config.OPERAND_DEST_NETWORK, text
+
+        elif combo.itemData(what_idx) == self.FIELD_REGEX_HOST:
+            parts = combo.currentText().split(' ')
+            text = parts[len(parts)-1]
+            # ^(|.*\.)yahoo\.com
+            dsthost = r'\.'.join(text.split('.')).replace("*", "")
+            dsthost = r'^(|.*\.)%s$' % dsthost[2:]
+            return Config.RULE_TYPE_REGEXP, Config.OPERAND_DEST_HOST, dsthost
+
+        elif combo.itemData(what_idx) == self.FIELD_REGEX_IP:
+            parts = combo.currentText().split(' ')
+            text = parts[len(parts)-1]
+            return Config.RULE_TYPE_REGEXP, Config.OPERAND_DEST_IP, "%s" % r'\.'.join(text.split('.')).replace("*", ".*")
+
+        elif combo.itemData(what_idx) == self.FIELD_APPIMAGE:
+            appimage_bin = os.path.basename(con.process_path)
+            appimage_path = os.path.dirname(con.process_path).replace('.', r'\.')
+            appimage_path = appimage_path[0:len(self.APPIMAGE_PREFIX)+7]
+            return Config.RULE_TYPE_REGEXP, Config.OPERAND_PROCESS_PATH, r'^{0}[0-9A-Za-z]{{6}}\/.*{1}$'.format(appimage_path, appimage_bin)
+
+    def _on_action_clicked(self, action):
+        self._default_action = action
+        self._send_rule()
+
+    def _on_deny_btn_clicked(self, action):
+        self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+        if self._default_action == Config.ACTION_ALLOW_IDX:
+            self._default_action = Config.ACTION_DENY_IDX
+        self._send_rule()
+
+    def _is_list_rule(self):
+        return self.checkUserID.isChecked() or self.checkDstPort.isChecked() or self.checkDstIP.isChecked()
+
+    def _get_rule_name(self, rule):
+        rule_temp_name = slugify("%s %s" % (rule.action, rule.duration))
+        if self._is_list_rule():
+            rule_temp_name = "%s-list" % rule_temp_name
+        else:
+            rule_temp_name = "%s-simple" % rule_temp_name
+        rule_temp_name = slugify("%s %s" % (rule_temp_name, rule.operator.data))
+
+        return rule_temp_name[:128]
+
+    def _send_rule(self):
+        try:
+            self._cfg.setSettings("promptDialog/geometry", self.saveGeometry())
+            self._rule = ui_pb2.Rule(name="user.choice")
+            self._rule.created = int(datetime.now().timestamp())
+            self._rule.enabled = True
+            self._rule.duration = self._get_duration(self.durationCombo.currentIndex())
+
+            self._rule.action = Config.ACTION_ALLOW
+            if self._default_action == Config.ACTION_DENY_IDX:
+                self._rule.action = Config.ACTION_DENY
+            elif self._default_action == Config.ACTION_REJECT_IDX:
+                self._rule.action = Config.ACTION_REJECT
+
+            what_idx = self.whatCombo.currentIndex()
+            self._rule.operator.type, self._rule.operator.operand, self._rule.operator.data = self._get_combo_operator(self.whatCombo, what_idx, self._con)
+            if self._rule.operator.data == "":
+                print("Invalid rule, discarding: ", self._rule)
+                self._rule = None
+                return
+
+            rule_temp_name = self._get_rule_name(self._rule)
+            self._rule.name = rule_temp_name
+
+            # TODO: move to a method
+            data=[]
+            if self.checkDstIP.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_DST_IP:
+                _type, _operand, _data = self._get_combo_operator(self.whatIPCombo, self.whatIPCombo.currentIndex(), self._con)
+                data.append({"type": _type, "operand": _operand, "data": _data})
+                rule_temp_name = slugify("%s %s" % (rule_temp_name, _data))
+
+            if self.checkDstPort.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_DST_PORT:
+                data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": Config.OPERAND_DEST_PORT, "data": str(self._con.dst_port)})
+                rule_temp_name = slugify("%s %s" % (rule_temp_name, str(self._con.dst_port)))
+
+            if self.checkUserID.isChecked() and self.whatCombo.itemData(what_idx) != self.FIELD_USER_ID:
+                data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": Config.OPERAND_USER_ID, "data": str(self._con.user_id)})
+                rule_temp_name = slugify("%s %s" % (rule_temp_name, str(self._con.user_id)))
+
+            is_list_rule = self._is_list_rule()
+
+            # If the user has selected to filter by cmdline, but the launched
+            # command path is not absolute or the first component contains
+            # "/proc/" (/proc/self/fd.., /proc/1234/fd...), we can't trust it.
+            # In these cases, also filter by the absolute path to the binary.
+            if self._rule.operator.operand == Config.OPERAND_PROCESS_COMMAND:
+                proc_args = " ".join(self._con.process_args)
+                proc_args = proc_args.split(" ")
+                if os.path.isabs(proc_args[0]) == False or proc_args[0].startswith("/proc"):
+                    is_list_rule = True
+                    data.append({"type": Config.RULE_TYPE_SIMPLE, "operand": Config.OPERAND_PROCESS_PATH, "data": str(self._con.process_path)})
+
+            if is_list_rule:
+                data.append({
+                    "type": self._rule.operator.type,
+                    "operand": self._rule.operator.operand,
+                    "data": self._rule.operator.data
+                })
+                # We need to send back the operator list to the AskRule() call
+                # as json string, in order to add it to the DB.
+                self._rule.operator.data = json.dumps(data)
+                self._rule.operator.type = Config.RULE_TYPE_LIST
+                self._rule.operator.operand = Config.RULE_TYPE_LIST
+                for op in data:
+                    self._rule.operator.list.extend([
+                        ui_pb2.Operator(
+                            type=op['type'],
+                            operand=op['operand'],
+                            sensitive=False if op.get('sensitive') == None else op['sensitive'],
+                            data="" if op.get('data') == None else op['data']
+                        )
+                    ])
+
+            exists = self._rules.exists(self._rule, self._peer)
+            if not exists:
+                self._rule.name = self._rules.new_unique_name(rule_temp_name, self._peer, "")
+
+            self.hide()
+            if self._ischeckAdvanceded:
+                self.checkAdvanced.toggle()
+            self._ischeckAdvanceded = False
+
+        except Exception as e:
+            print("[pop-up] exception creating a rule:", e)
+        finally:
+            # signal that the user took a decision and
+            # a new rule is available
+            self._done.set()
+            self.hide()
diff --git a/ui/opensnitch/dialogs/ruleseditor.py b/ui/opensnitch/dialogs/ruleseditor.py
new file mode 100644 (file)
index 0000000..84d99fd
--- /dev/null
@@ -0,0 +1,1066 @@
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QCoreApplication as QC
+from slugify import slugify
+from datetime import datetime
+import re
+import sys
+import os
+import pwd
+import time
+import ipaddress
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from opensnitch.config import Config
+from opensnitch.nodes import Nodes
+from opensnitch.database import Database
+from opensnitch.database.enums import RuleFields, ConnFields
+from opensnitch.version import version
+from opensnitch.utils import (
+    Message,
+    FileDialog,
+    Icons,
+    NetworkInterfaces,
+    qvalidator
+)
+from opensnitch.rules import Rule, Rules
+
+DIALOG_UI_PATH = "%s/../res/ruleseditor.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class RulesEditorDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+
+    LOG_TAG = "[rules editor]"
+    classA_net = r'10\.\d{1,3}\.\d{1,3}\.\d{1,3}'
+    classB_net = r'172\.1[6-9]\.\d+\.\d+|172\.2[0-9]\.\d+\.\d+|172\.3[0-1]+\.\d{1,3}\.\d{1,3}'
+    classC_net = r'192\.168\.\d{1,3}\.\d{1,3}'
+    others_net = r'127\.\d{1,3}\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3}'
+    multinets = r'2[32][23459]\.\d{1,3}\.\d{1,3}\.\d{1,3}'
+    MULTICAST_RANGE = "^(" + multinets + ")$"
+    LAN_RANGES = "^(" + others_net + "|" + classC_net + "|" + classB_net + "|" + classA_net + "|::1|f[cde].*::.*)$"
+    LAN_LABEL = "LAN"
+    MULTICAST_LABEL = "MULTICAST"
+
+    INVALID_RULE_NAME_CHARS = '/'
+
+    ADD_RULE = 0
+    EDIT_RULE = 1
+    WORK_MODE = ADD_RULE
+
+    PW_USER = 0
+    PW_UID = 2
+
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+
+    def __init__(self, parent=None, _rule=None, appicon=None):
+        super(RulesEditorDialog, self).__init__(parent)
+
+        self._notifications_sent = {}
+        self._nodes = Nodes.instance()
+        self._db = Database.instance()
+        self._rules = Rules.instance()
+        self._notification_callback.connect(self._cb_notification_callback)
+        self._old_rule_name = None
+
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+
+        self.ruleNameValidator = qvalidator.RestrictChars(RulesEditorDialog.INVALID_RULE_NAME_CHARS)
+        self.ruleNameValidator.result.connect(self._cb_rule_name_validator_result)
+        self.ruleNameEdit.setValidator(self.ruleNameValidator)
+
+        self.buttonBox.setStandardButtons(
+            QtWidgets.QDialogButtonBox.Help |
+            QtWidgets.QDialogButtonBox.Reset |
+            QtWidgets.QDialogButtonBox.Close |
+            QtWidgets.QDialogButtonBox.Save
+        )
+
+        self.buttonBox.button(QtWidgets.QDialogButtonBox.Reset).clicked.connect(self._cb_reset_clicked)
+        self.buttonBox.button(QtWidgets.QDialogButtonBox.Close).clicked.connect(self._cb_close_clicked)
+        self.buttonBox.button(QtWidgets.QDialogButtonBox.Save).clicked.connect(self._cb_save_clicked)
+        self.buttonBox.button(QtWidgets.QDialogButtonBox.Help).clicked.connect(self._cb_help_clicked)
+        self.selectListButton.clicked.connect(self._cb_select_list_button_clicked)
+        self.selectListRegexpButton.clicked.connect(self._cb_select_regexp_list_button_clicked)
+        self.selectIPsListButton.clicked.connect(self._cb_select_ips_list_button_clicked)
+        self.selectNetsListButton.clicked.connect(self._cb_select_nets_list_button_clicked)
+        self.protoCheck.toggled.connect(self._cb_proto_check_toggled)
+        self.procCheck.toggled.connect(self._cb_proc_check_toggled)
+        self.cmdlineCheck.toggled.connect(self._cb_cmdline_check_toggled)
+        self.ifaceCheck.toggled.connect(self._cb_iface_check_toggled)
+        self.dstPortCheck.toggled.connect(self._cb_dstport_check_toggled)
+        self.srcPortCheck.toggled.connect(self._cb_srcport_check_toggled)
+        self.uidCheck.toggled.connect(self._cb_uid_check_toggled)
+        self.pidCheck.toggled.connect(self._cb_pid_check_toggled)
+        self.srcIPCheck.toggled.connect(self._cb_srcip_check_toggled)
+        self.dstIPCheck.toggled.connect(self._cb_dstip_check_toggled)
+        self.dstHostCheck.toggled.connect(self._cb_dsthost_check_toggled)
+        self.dstListsCheck.toggled.connect(self._cb_dstlists_check_toggled)
+        self.dstListRegexpCheck.toggled.connect(self._cb_dstregexplists_check_toggled)
+        self.dstListIPsCheck.toggled.connect(self._cb_dstiplists_check_toggled)
+        self.dstListNetsCheck.toggled.connect(self._cb_dstnetlists_check_toggled)
+        self.uidCombo.currentIndexChanged.connect(self._cb_uid_combo_changed)
+
+        self._users_list = pwd.getpwall()
+
+        if QtGui.QIcon.hasThemeIcon("emblem-default"):
+            return
+
+        applyIcon = Icons.new(self, "emblem-default")
+        denyIcon = Icons.new(self, "emblem-important")
+        rejectIcon = Icons.new(self, "window-close")
+        openIcon = Icons.new(self, "document-open")
+        self.actionAllowRadio.setIcon(applyIcon)
+        self.actionDenyRadio.setIcon(denyIcon)
+        self.actionRejectRadio.setIcon(rejectIcon)
+        self.selectListButton.setIcon(openIcon)
+        self.selectListRegexpButton.setIcon(openIcon)
+        self.selectNetsListButton.setIcon(openIcon)
+        self.selectIPsListButton.setIcon(openIcon)
+
+        if _rule != None:
+            self._load_rule(rule=_rule)
+
+    def showEvent(self, event):
+        super(RulesEditorDialog, self).showEvent(event)
+
+        # save old combo values so we don't overwrite them here.
+        oldIface = self.ifaceCombo.currentText()
+        oldUid = self.uidCombo.currentText()
+        self.ifaceCombo.clear()
+        self.uidCombo.clear()
+        if self._nodes.is_local(self.nodesCombo.currentText()):
+            self.ifaceCombo.addItems(NetworkInterfaces.list().keys())
+            try:
+                for ip in NetworkInterfaces.list().values():
+                    if self.srcIPCombo.findText(ip) == -1:
+                        self.srcIPCombo.insertItem(0, ip)
+                    if self.dstIPCombo.findText(ip) == -1:
+                        self.dstIPCombo.insertItem(0, ip)
+
+                self._users_list = pwd.getpwall()
+                self.uidCombo.blockSignals(True);
+                for user in self._users_list:
+                    self.uidCombo.addItem("{0} ({1})".format(user[self.PW_USER], user[self.PW_UID]), user[self.PW_UID])
+            except Exception as e:
+                print("[ruleseditor] Error adding IPs:", e)
+            finally:
+                self.uidCombo.blockSignals(False);
+        self.ifaceCombo.setCurrentText(oldIface)
+        self.uidCombo.setCurrentText(oldUid)
+
+    def _bool(self, s):
+        return s == 'True'
+
+    def _cb_rule_name_validator_result(self, result):
+        if result == QtGui.QValidator.Invalid:
+            self._set_status_error(
+                QC.translate("rules",
+                             "Invalid rule name (not allowed characters: '{0}' )".format(RulesEditorDialog.INVALID_RULE_NAME_CHARS)
+                             )
+            )
+        else:
+            self._set_status_message("")
+
+    def _cb_accept_clicked(self):
+        pass
+
+    def _cb_close_clicked(self):
+        self.hide()
+
+    def _cb_reset_clicked(self):
+        self._reset_state()
+
+    def _cb_help_clicked(self):
+        QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_URL))
+
+    def _cb_select_list_button_clicked(self):
+        dirName = FileDialog.select_dir(self, self.dstListsLine.text())
+        if dirName != None and dirName != "":
+            self.dstListsLine.setText(dirName)
+
+    def _cb_select_nets_list_button_clicked(self):
+        dirName = FileDialog.select_dir(self, self.dstListNetsLine.text())
+        if dirName != None and dirName != "":
+            self.dstListNetsLine.setText(dirName)
+
+    def _cb_select_ips_list_button_clicked(self):
+        dirName = FileDialog.select_dir(self, self.dstListIPsLine.text())
+        if dirName != None and dirName != "":
+            self.dstListIPsLine.setText(dirName)
+
+    def _cb_select_regexp_list_button_clicked(self):
+        dirName = FileDialog.select_dir(self, self.dstRegexpListsLine.text())
+        if dirName != None and dirName != "":
+            self.dstRegexpListsLine.setText(dirName)
+
+    def _cb_proto_check_toggled(self, state):
+        self.protoCombo.setEnabled(state)
+
+    def _cb_proc_check_toggled(self, state):
+        self.procLine.setEnabled(state)
+        self.checkProcRegexp.setEnabled(state)
+
+    def _cb_cmdline_check_toggled(self, state):
+        self.cmdlineLine.setEnabled(state)
+        self.checkCmdlineRegexp.setEnabled(state)
+
+    def _cb_iface_check_toggled(self, state):
+        self.ifaceCombo.setEnabled(state)
+
+    def _cb_dstport_check_toggled(self, state):
+        self.dstPortLine.setEnabled(state)
+
+    def _cb_srcport_check_toggled(self, state):
+        self.srcPortLine.setEnabled(state)
+
+    def _cb_uid_check_toggled(self, state):
+        self.uidCombo.setEnabled(state)
+
+    def _cb_pid_check_toggled(self, state):
+        self.pidLine.setEnabled(state)
+
+    def _cb_srcip_check_toggled(self, state):
+        self.srcIPCombo.setEnabled(state)
+
+    def _cb_dstip_check_toggled(self, state):
+        self.dstIPCombo.setEnabled(state)
+
+    def _cb_dsthost_check_toggled(self, state):
+        self.dstHostLine.setEnabled(state)
+
+    def _cb_dstlists_check_toggled(self, state):
+        self.dstListsLine.setEnabled(state)
+        self.selectListButton.setEnabled(state)
+
+    def _cb_dstregexplists_check_toggled(self, state):
+        self.dstRegexpListsLine.setEnabled(state)
+        self.selectListRegexpButton.setEnabled(state)
+
+    def _cb_dstiplists_check_toggled(self, state):
+        self.dstListIPsLine.setEnabled(state)
+        self.selectIPsListButton.setEnabled(state)
+
+    def _cb_dstnetlists_check_toggled(self, state):
+        self.dstListNetsLine.setEnabled(state)
+        self.selectNetsListButton.setEnabled(state)
+
+    def _cb_uid_combo_changed(self, index):
+        self.uidCombo.setCurrentText(str(self._users_list[index][self.PW_UID]))
+
+    def _set_status_error(self, msg):
+        self.statusLabel.setStyleSheet('color: red')
+        self.statusLabel.setText(msg)
+
+    def _set_status_message(self, msg):
+        self.statusLabel.setStyleSheet('color: green')
+        self.statusLabel.setText(msg)
+
+    def _cb_save_clicked(self):
+        if self.nodesCombo.count() == 0:
+            self._set_status_error(QC.translate("rules", "There're no nodes connected."))
+            return
+
+        rule_name = self.ruleNameEdit.text()
+        if rule_name == "":
+            return
+
+        node = self.nodesCombo.currentText()
+        # avoid to overwrite rules when:
+        # - adding a new rule.
+        # - when a rule is renamed, i.e., the rule is edited or added and the
+        #   user changes the name.
+        if self.WORK_MODE == self.ADD_RULE and self._db.get_rule(rule_name, node).next() == True:
+            self._set_status_error(QC.translate("rules", "There's already a rule with this name."))
+            return
+        elif self.WORK_MODE == self.EDIT_RULE and rule_name != self._old_rule_name and \
+            self._db.get_rule(rule_name, node).next() == True:
+            self._set_status_error(QC.translate("rules", "There's already a rule with this name."))
+            return
+
+        result, error = self._save_rule()
+        if result == False:
+            self._set_status_error(error)
+            return
+
+        self._add_rule()
+        if self._old_rule_name != None and self._old_rule_name != self.rule.name:
+            self._delete_rule()
+
+        self._old_rule_name = rule_name
+
+        # after adding a new rule, we enter into EDIT mode, to allow further
+        # changes without closing the dialog.
+        if self.WORK_MODE == self.ADD_RULE:
+            self.WORK_MODE = self.EDIT_RULE
+
+        self._rules.updated.emit(0)
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def _cb_notification_callback(self, reply):
+        #print(self.LOG_TAG, "Rule notification received: ", reply.id, reply.code)
+        if reply.id in self._notifications_sent:
+            if reply.code == ui_pb2.OK:
+                self._set_status_message(QC.translate("rules", "Rule applied."))
+            else:
+                self._set_status_error(QC.translate("rules", "Error applying rule: {0}").format(reply.data))
+
+            del self._notifications_sent[reply.id]
+
+    def _get_duration(self, duration_idx):
+        if duration_idx == 0:
+            return Config.DURATION_ONCE
+        elif duration_idx == 1:
+            return Config.DURATION_30s
+        elif duration_idx == 2:
+            return Config.DURATION_5m
+        elif duration_idx == 3:
+            return Config.DURATION_15m
+        elif duration_idx == 4:
+            return Config.DURATION_30m
+        elif duration_idx == 5:
+            return Config.DURATION_1h
+        elif duration_idx == 6:
+            return Config.DURATION_UNTIL_RESTART
+        else:
+            return Config.DURATION_ALWAYS
+
+    def _load_duration(self, duration):
+        if duration == Config.DURATION_ONCE:
+            return 0
+        elif duration == Config.DURATION_30s:
+            return 1
+        elif duration == Config.DURATION_5m:
+            return 2
+        elif duration == Config.DURATION_15m:
+            return 3
+        elif duration == Config.DURATION_30m:
+            return 4
+        elif duration == Config.DURATION_1h:
+            return 5
+        elif duration == Config.DURATION_UNTIL_RESTART:
+            return 6
+        else:
+            # always
+            return 7
+
+    def _is_regex(self, text):
+        charset="\\*{[|^?$"
+        for c in charset:
+            if c in text:
+                return True
+        return False
+
+    def _is_valid_regex(self, regex):
+        try:
+            re.compile(regex)
+            return True
+        except re.error as e:
+            self.statusLabel.setText(str(e))
+            return False
+
+    def _is_valid_list_path(self, listWidget):
+        if listWidget.text() == "":
+            return QC.translate("rules", "Lists field cannot be empty")
+        if self._nodes.is_local(self.nodesCombo.currentText()) and \
+            self.nodeApplyAllCheck.isChecked() == False and \
+            os.path.isdir(listWidget.text()) == False:
+            return QC.translate("rules", "Lists field must be a directory")
+
+        return None
+
+    def set_fields_from_connection(self, records):
+        self.nodesCombo.setCurrentText(records.value(ConnFields.Node))
+        self.protoCombo.setCurrentText(records.value(ConnFields.Protocol).upper())
+        self.srcIPCombo.setCurrentText(records.value(ConnFields.SrcIP))
+        self.dstIPCombo.setCurrentText(records.value(ConnFields.DstIP))
+        self.dstHostLine.setText(records.value(ConnFields.DstHost))
+        self.dstPortLine.setText(records.value(ConnFields.DstPort))
+        self.srcPortLine.setText(records.value(ConnFields.SrcPort))
+        self.uidCombo.setCurrentText(records.value(ConnFields.UID))
+        self.pidLine.setText(records.value(ConnFields.PID))
+        self.procLine.setText(records.value(ConnFields.Process))
+        self.cmdlineLine.setText(records.value(ConnFields.Cmdline))
+
+    def _reset_state(self):
+        self._old_rule_name = None
+        self.rule = None
+
+        self.ruleNameEdit.setText("")
+        self.ruleDescEdit.setPlainText("")
+        self.statusLabel.setText("")
+
+        self.actionDenyRadio.setChecked(True)
+        self.durationCombo.setCurrentIndex(0)
+
+        self.protoCheck.setChecked(False)
+        self.protoCombo.setCurrentText("")
+
+        self.procCheck.setChecked(False)
+        self.checkProcRegexp.setEnabled(False)
+        self.checkProcRegexp.setChecked(False)
+        self.procLine.setText("")
+
+        self.cmdlineCheck.setChecked(False)
+        self.checkCmdlineRegexp.setEnabled(False)
+        self.checkCmdlineRegexp.setChecked(False)
+        self.cmdlineLine.setText("")
+
+        self.uidCheck.setChecked(False)
+        self.uidCombo.setCurrentText("")
+
+        self.pidCheck.setChecked(False)
+        self.pidLine.setText("")
+
+        self.ifaceCheck.setChecked(False)
+        self.ifaceCombo.setCurrentText("")
+
+        self.dstPortCheck.setChecked(False)
+        self.dstPortLine.setText("")
+
+        self.srcPortCheck.setChecked(False)
+        self.srcPortLine.setText("")
+
+        self.srcIPCheck.setChecked(False)
+        self.srcIPCombo.setCurrentText("")
+
+        self.dstIPCheck.setChecked(False)
+        self.dstIPCombo.setCurrentText("")
+
+        self.dstHostCheck.setChecked(False)
+        self.dstHostLine.setText("")
+
+        self.selectListButton.setEnabled(False)
+        self.dstListsCheck.setChecked(False)
+        self.dstListsLine.setText("")
+
+        self.selectListRegexpButton.setEnabled(False)
+        self.dstListRegexpCheck.setChecked(False)
+        self.dstRegexpListsLine.setText("")
+
+        self.selectIPsListButton.setEnabled(False)
+        self.dstListIPsCheck.setChecked(False)
+        self.dstListIPsLine.setText("")
+
+        self.selectNetsListButton.setEnabled(False)
+        self.dstListNetsCheck.setChecked(False)
+        self.dstListNetsLine.setText("")
+
+    def _load_rule(self, addr=None, rule=None):
+        if self._load_nodes(addr) == False:
+            return False
+
+        self.ruleNameEdit.setText(rule.name)
+        self.ruleDescEdit.setPlainText(rule.description)
+        self.enableCheck.setChecked(rule.enabled)
+        self.precedenceCheck.setChecked(rule.precedence)
+        self.nologCheck.setChecked(rule.nolog)
+        if rule.action == Config.ACTION_DENY:
+            self.actionDenyRadio.setChecked(True)
+        elif rule.action == Config.ACTION_ALLOW:
+            self.actionAllowRadio.setChecked(True)
+        elif rule.action == Config.ACTION_REJECT:
+            self.actionRejectRadio.setChecked(True)
+
+        self.durationCombo.setCurrentIndex(self._load_duration(self.rule.duration))
+
+        if self.rule.operator.type != Config.RULE_TYPE_LIST:
+            self._load_rule_operator(self.rule.operator)
+        else:
+            for op in self.rule.operator.list:
+                self._load_rule_operator(op)
+
+        return True
+
+    def _load_rule_operator(self, operator):
+        self.sensitiveCheck.setChecked(operator.sensitive)
+        if operator.operand == Config.OPERAND_PROTOCOL:
+            self.protoCheck.setChecked(True)
+            self.protoCombo.setEnabled(True)
+            self.protoCombo.setCurrentText(operator.data.upper())
+
+        if operator.operand == Config.OPERAND_PROCESS_PATH:
+            self.procCheck.setChecked(True)
+            self.procLine.setEnabled(True)
+            self.procLine.setText(operator.data)
+            self.checkProcRegexp.setEnabled(True)
+            self.checkProcRegexp.setChecked(operator.type == Config.RULE_TYPE_REGEXP)
+
+        if operator.operand == Config.OPERAND_PROCESS_COMMAND:
+            self.cmdlineCheck.setChecked(True)
+            self.cmdlineLine.setEnabled(True)
+            self.cmdlineLine.setText(operator.data)
+            self.checkCmdlineRegexp.setEnabled(True)
+            self.checkCmdlineRegexp.setChecked(operator.type == Config.RULE_TYPE_REGEXP)
+
+        if operator.operand == Config.OPERAND_USER_ID:
+            self.uidCheck.setChecked(True)
+            self.uidCombo.setEnabled(True)
+            self.uidCombo.setCurrentText(operator.data)
+
+        if operator.operand == Config.OPERAND_PROCESS_ID:
+            self.pidCheck.setChecked(True)
+            self.pidLine.setEnabled(True)
+            self.pidLine.setText(operator.data)
+
+        if operator.operand == Config.OPERAND_IFACE_OUT:
+            self.ifaceCheck.setChecked(True)
+            self.ifaceCombo.setEnabled(True)
+            self.ifaceCombo.setCurrentText(operator.data)
+
+        if operator.operand == Config.OPERAND_SOURCE_PORT:
+            self.srcPortCheck.setChecked(True)
+            self.srcPortLine.setEnabled(True)
+            self.srcPortLine.setText(operator.data)
+
+        if operator.operand == Config.OPERAND_DEST_PORT:
+            self.dstPortCheck.setChecked(True)
+            self.dstPortLine.setEnabled(True)
+            self.dstPortLine.setText(operator.data)
+
+        if operator.operand == Config.OPERAND_SOURCE_IP or operator.operand == Config.OPERAND_SOURCE_NETWORK:
+            self.srcIPCheck.setChecked(True)
+            self.srcIPCombo.setEnabled(True)
+            if operator.data == self.LAN_RANGES:
+                self.srcIPCombo.setCurrentText(self.LAN_LABEL)
+            elif operator.data == self.MULTICAST_RANGE:
+                self.srcIPCombo.setCurrentText(self.MULTICAST_LABEL)
+            else:
+                self.srcIPCombo.setCurrentText(operator.data)
+
+        if operator.operand == Config.OPERAND_DEST_IP or operator.operand == Config.OPERAND_DEST_NETWORK:
+            self.dstIPCheck.setChecked(True)
+            self.dstIPCombo.setEnabled(True)
+            if operator.data == self.LAN_RANGES:
+                self.dstIPCombo.setCurrentText(self.LAN_LABEL)
+            elif operator.data == self.MULTICAST_RANGE:
+                self.dstIPCombo.setCurrentText(self.MULTICAST_LABEL)
+            else:
+                self.dstIPCombo.setCurrentText(operator.data)
+
+        if operator.operand == Config.OPERAND_DEST_HOST:
+            self.dstHostCheck.setChecked(True)
+            self.dstHostLine.setEnabled(True)
+            self.dstHostLine.setText(operator.data)
+
+        if operator.operand == Config.OPERAND_LIST_DOMAINS:
+            self.dstListsCheck.setChecked(True)
+            self.dstListsCheck.setEnabled(True)
+            self.dstListsLine.setText(operator.data)
+            self.selectListButton.setEnabled(True)
+
+        if operator.operand == Config.OPERAND_LIST_DOMAINS_REGEXP:
+            self.dstListRegexpCheck.setChecked(True)
+            self.dstListRegexpCheck.setEnabled(True)
+            self.dstRegexpListsLine.setText(operator.data)
+            self.selectListRegexpButton.setEnabled(True)
+
+        if operator.operand == Config.OPERAND_LIST_IPS:
+            self.dstListIPsCheck.setChecked(True)
+            self.dstListIPsCheck.setEnabled(True)
+            self.dstListIPsLine.setText(operator.data)
+            self.selectIPsListButton.setEnabled(True)
+
+        if operator.operand == Config.OPERAND_LIST_NETS:
+            self.dstListNetsCheck.setChecked(True)
+            self.dstListNetsCheck.setEnabled(True)
+            self.dstListNetsLine.setText(operator.data)
+            self.selectNetsListButton.setEnabled(True)
+
+    def _load_nodes(self, addr=None):
+        try:
+            self.nodesCombo.clear()
+            self._node_list = self._nodes.get()
+
+            if addr != None and addr not in self._node_list:
+                Message.ok(QC.translate("rules", "<b>Error loading rule</b>"),
+                        QC.translate("rules", "node {0} not connected".format(addr)),
+                        QtWidgets.QMessageBox.Warning)
+                return False
+
+            if len(self._node_list) < 2:
+                self.nodeApplyAllCheck.setVisible(False)
+
+            for node in self._node_list:
+                self.nodesCombo.addItem(node)
+
+            if addr != None:
+                self.nodesCombo.setCurrentText(addr)
+
+        except Exception as e:
+            print(self.LOG_TAG, "exception loading nodes: ", e, addr)
+            return False
+
+        return True
+
+    def _insert_rule_to_db(self, node_addr):
+        # the order of the fields doesn't matter here, as long as we use the
+        # name of the field.
+        self._rules.add_rules(node_addr, [self.rule])
+
+    def _add_rule(self):
+        try:
+            if self.nodeApplyAllCheck.isChecked():
+                for pos in range(self.nodesCombo.count()):
+                    self._insert_rule_to_db(self.nodesCombo.itemText(pos))
+            else:
+                self._insert_rule_to_db(self.nodesCombo.currentText())
+
+            notif = ui_pb2.Notification(
+                    id=int(str(time.time()).replace(".", "")),
+                    type=ui_pb2.CHANGE_RULE,
+                    data="",
+                    rules=[self.rule])
+            if self.nodeApplyAllCheck.isChecked():
+                nid = self._nodes.send_notifications(notif, self._notification_callback)
+            else:
+                nid = self._nodes.send_notification(self.nodesCombo.currentText(), notif, self._notification_callback)
+
+            self._notifications_sent[nid] = notif
+        except Exception as e:
+            print(self.LOG_TAG, "add_rule() exception: ", e)
+
+    def _delete_rule(self):
+        try:
+            # if the rule name has changed, we need to remove the old one
+            if self._old_rule_name != self.rule.name:
+                node = self.nodesCombo.currentText()
+                old_rule = self.rule
+                old_rule.name = self._old_rule_name
+                if self.nodeApplyAllCheck.isChecked():
+                    nid, noti = self._nodes.delete_rule(rule_name=self._old_rule_name, addr=None, callback=self._notification_callback)
+                    self._notifications_sent[nid] = noti
+                else:
+                    nid, noti = self._nodes.delete_rule(self._old_rule_name, node, self._notification_callback)
+                    self._notifications_sent[nid] = noti
+
+        except Exception as e:
+            print(self.LOG_TAG, "delete_rule() exception: ", e)
+
+
+    def _save_rule(self):
+        """
+        Create a new rule based on the fields selected.
+
+        Ensure that some constraints are met:
+        - Determine if a field can be a regexp.
+        - Validate regexp.
+        - Fields cannot be empty.
+        - If the user has not provided a rule name, auto assign one.
+        """
+        self.rule = ui_pb2.Rule()
+        self.rule.created = int(datetime.now().timestamp())
+        self.rule.name = self.ruleNameEdit.text()
+        self.rule.description = self.ruleDescEdit.toPlainText()
+        self.rule.enabled = self.enableCheck.isChecked()
+        self.rule.precedence = self.precedenceCheck.isChecked()
+        self.rule.nolog = self.nologCheck.isChecked()
+        self.rule.operator.type = Config.RULE_TYPE_SIMPLE
+        self.rule.action = Config.ACTION_DENY
+        if self.actionAllowRadio.isChecked():
+            self.rule.action = Config.ACTION_ALLOW
+        elif self.actionRejectRadio.isChecked():
+            self.rule.action = Config.ACTION_REJECT
+
+        self.rule.duration = self._get_duration(self.durationCombo.currentIndex())
+
+        # FIXME: there should be a sensitive checkbox per operand
+        self.rule.operator.sensitive = self.sensitiveCheck.isChecked()
+        rule_data = []
+        if self.protoCheck.isChecked():
+            if self.protoCombo.currentText() == "":
+                return False, QC.translate("rules", "protocol can not be empty, or uncheck it")
+
+            self.rule.operator.operand = Config.OPERAND_PROTOCOL
+            self.rule.operator.data = self.protoCombo.currentText()
+            rule_data.append(
+                    {
+                        "type": Config.RULE_TYPE_SIMPLE,
+                        "operand": Config.OPERAND_PROTOCOL,
+                        "data": self.protoCombo.currentText().lower(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self._is_regex(self.protoCombo.currentText()):
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.protoCombo.currentText()) == False:
+                    return False, QC.translate("rules", "Protocol regexp error")
+
+        if self.procCheck.isChecked():
+            if self.procLine.text() == "":
+                return False, QC.translate("rules", "process path can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_PROCESS_PATH
+            self.rule.operator.data = self.procLine.text()
+            rule_data.append(
+                    {
+                        "type": Config.RULE_TYPE_SIMPLE,
+                        "operand": Config.OPERAND_PROCESS_PATH,
+                        "data": self.procLine.text(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self.checkProcRegexp.isChecked():
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.procLine.text()) == False:
+                    return False, QC.translate("rules", "Process path regexp error")
+
+        if self.cmdlineCheck.isChecked():
+            if self.cmdlineLine.text() == "":
+                return False, QC.translate("rules", "command line can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_PROCESS_COMMAND
+            self.rule.operator.data = self.cmdlineLine.text()
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_SIMPLE,
+                        'operand': Config.OPERAND_PROCESS_COMMAND,
+                        'data': self.cmdlineLine.text(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self.checkCmdlineRegexp.isChecked():
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.cmdlineLine.text()) == False:
+                    return False, QC.translate("rules", "Command line regexp error")
+
+        if self.ifaceCheck.isChecked():
+            if self.ifaceCombo.currentText() == "":
+                return False, QC.translate("rules", "Network interface can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_IFACE_OUT
+            self.rule.operator.data = self.ifaceCombo.currentText()
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_SIMPLE,
+                        'operand': Config.OPERAND_IFACE_OUT,
+                        'data': self.ifaceCombo.currentText(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self._is_regex(self.ifaceCombo.currentText()):
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.ifaceCombo.currentText()) == False:
+                    return False, QC.translate("rules", "Network interface regexp error")
+
+        if self.srcPortCheck.isChecked():
+            if self.srcPortLine.text() == "":
+                return False, QC.translate("rules", "Source port can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_SOURCE_PORT
+            self.rule.operator.data = self.srcPortLine.text()
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_SIMPLE,
+                        'operand': Config.OPERAND_SOURCE_PORT,
+                        'data': self.srcPortLine.text(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self._is_regex(self.srcPortLine.text()):
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.srcPortLine.text()) == False:
+                    return False, QC.translate("rules", "Source port regexp error")
+
+        if self.dstPortCheck.isChecked():
+            if self.dstPortLine.text() == "":
+                return False, QC.translate("rules", "Dest port can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_DEST_PORT
+            self.rule.operator.data = self.dstPortLine.text()
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_SIMPLE,
+                        'operand': Config.OPERAND_DEST_PORT,
+                        'data': self.dstPortLine.text(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self._is_regex(self.dstPortLine.text()):
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.dstPortLine.text()) == False:
+                    return False, QC.translate("rules", "Dst port regexp error")
+
+        if self.dstHostCheck.isChecked():
+            if self.dstHostLine.text() == "":
+                return False, QC.translate("rules", "Dest host can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_DEST_HOST
+            self.rule.operator.data = self.dstHostLine.text()
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_SIMPLE,
+                        'operand': Config.OPERAND_DEST_HOST,
+                        'data': self.dstHostLine.text(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self._is_regex(self.dstHostLine.text()):
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.dstHostLine.text()) == False:
+                    return False, QC.translate("rules", "Dst host regexp error")
+
+        if self.srcIPCheck.isChecked():
+            if self.srcIPCombo.currentText() == "":
+                return False, QC.translate("rules", "Source IP/Network can not be empty")
+
+            srcIPtext = self.srcIPCombo.currentText()
+
+            if srcIPtext == self.LAN_LABEL:
+                self.rule.operator.operand = Config.OPERAND_SOURCE_IP
+                self.rule.operator.type = Config.RULE_TYPE_REGEXP
+                srcIPtext = self.LAN_RANGES
+            elif srcIPtext == self.MULTICAST_LABEL:
+                self.rule.operator.operand = Config.OPERAND_SOURCE_IP
+                self.rule.operator.type = Config.RULE_TYPE_REGEXP
+                srcIPtext = self.MULTICAST_RANGE
+            else:
+                try:
+                    if type(ipaddress.ip_address(self.srcIPCombo.currentText())) == ipaddress.IPv4Address \
+                    or type(ipaddress.ip_address(self.srcIPCombo.currentText())) == ipaddress.IPv6Address:
+                        self.rule.operator.operand = Config.OPERAND_SOURCE_IP
+                        self.rule.operator.type = Config.RULE_TYPE_SIMPLE
+                except Exception:
+                    self.rule.operator.operand = Config.OPERAND_SOURCE_NETWORK
+                    self.rule.operator.type = Config.RULE_TYPE_NETWORK
+
+                if self._is_regex(srcIPtext):
+                    self.rule.operator.operand = Config.OPERAND_SOURCE_IP
+                    self.rule.operator.type = Config.RULE_TYPE_REGEXP
+                    if self._is_valid_regex(self.srcIPCombo.currentText()) == False:
+                        return False, QC.translate("rules", "Source IP regexp error")
+
+            rule_data.append(
+                    {
+                        'type': self.rule.operator.type,
+                        'operand': self.rule.operator.operand,
+                        'data': srcIPtext,
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+
+        if self.dstIPCheck.isChecked():
+            if self.dstIPCombo.currentText() == "":
+                return False, QC.translate("rules", "Dest IP/Network can not be empty")
+
+            dstIPtext = self.dstIPCombo.currentText()
+
+            if dstIPtext == self.LAN_LABEL:
+                self.rule.operator.operand = Config.OPERAND_DEST_IP
+                self.rule.operator.type = Config.RULE_TYPE_REGEXP
+                dstIPtext = self.LAN_RANGES
+            elif dstIPtext == self.MULTICAST_LABEL:
+                self.rule.operator.operand = Config.OPERAND_DEST_IP
+                self.rule.operator.type = Config.RULE_TYPE_REGEXP
+                dstIPtext = self.MULTICAST_RANGE
+            else:
+                try:
+                    if type(ipaddress.ip_address(self.dstIPCombo.currentText())) == ipaddress.IPv4Address \
+                    or type(ipaddress.ip_address(self.dstIPCombo.currentText())) == ipaddress.IPv6Address:
+                        self.rule.operator.operand = Config.OPERAND_DEST_IP
+                        self.rule.operator.type = Config.RULE_TYPE_SIMPLE
+                except Exception:
+                    self.rule.operator.operand = Config.OPERAND_DEST_NETWORK
+                    self.rule.operator.type = Config.RULE_TYPE_NETWORK
+
+                if self._is_regex(dstIPtext):
+                    self.rule.operator.operand = Config.OPERAND_DEST_IP
+                    self.rule.operator.type = Config.RULE_TYPE_REGEXP
+                    if self._is_valid_regex(self.dstIPCombo.currentText()) == False:
+                        return False, QC.translate("rules", "Dst IP regexp error")
+
+            rule_data.append(
+                    {
+                        'type': self.rule.operator.type,
+                        'operand': self.rule.operator.operand,
+                        'data': dstIPtext,
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+
+        if self.uidCheck.isChecked():
+            uidType = Config.RULE_TYPE_SIMPLE
+            uid = self.uidCombo.currentText()
+
+            if uid == "":
+                return False, QC.translate("rules", "User ID can not be empty")
+
+            try:
+                # sometimes when loading a rule, instead of the UID, the format
+                # "user (uid)" is set. So try to parse it, in order not to save
+                # a wrong uid.
+                uidtmp = uid.split(" ")
+                if len(uidtmp) == 1:
+                    int(uidtmp[0])
+                else:
+                    uid = str(pwd.getpwnam(uidtmp[0])[self.PW_UID])
+            except:
+                # if it's not a digit and nor a system user (user (id)), see if
+                # it's a regexp.
+                if self._is_regex(self.uidCombo.currentText()):
+                    uidType = Config.RULE_TYPE_REGEXP
+                    if self._is_valid_regex(self.uidCombo.currentText()) == False:
+                        return False, QC.translate("rules", "User ID regexp error")
+
+                else:
+                    return False, QC.translate("rules", "Invalid UID, it must be a digit.")
+
+            self.rule.operator.operand = Config.OPERAND_USER_ID
+            self.rule.operator.data = self.uidCombo.currentText()
+            rule_data.append(
+                    {
+                        'type': uidType,
+                        'operand': Config.OPERAND_USER_ID,
+                        'data': uid,
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+
+        if self.pidCheck.isChecked():
+            if self.pidLine.text() == "":
+                return False, QC.translate("rules", "PID field can not be empty")
+
+            self.rule.operator.operand = Config.OPERAND_PROCESS_ID
+            self.rule.operator.data = self.pidLine.text()
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_SIMPLE,
+                        'operand': Config.OPERAND_PROCESS_ID,
+                        'data': self.pidLine.text(),
+                        "sensitive": self.sensitiveCheck.isChecked()
+                        })
+            if self._is_regex(self.pidLine.text()):
+                rule_data[len(rule_data)-1]['type'] = Config.RULE_TYPE_REGEXP
+                if self._is_valid_regex(self.pidLine.text()) == False:
+                    return False, QC.translate("rules", "PID field regexp error")
+
+        if self.dstListsCheck.isChecked():
+            error = self._is_valid_list_path(self.dstListsLine)
+            if error:
+                return False, error
+
+            self.rule.operator.type = Config.RULE_TYPE_LISTS
+            self.rule.operator.operand = Config.OPERAND_LIST_DOMAINS
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_LISTS,
+                        'operand': Config.OPERAND_LIST_DOMAINS,
+                        'data': self.dstListsLine.text(),
+                        'sensitive': self.sensitiveCheck.isChecked()
+                        })
+            self.rule.operator.data = ""
+
+        if self.dstListRegexpCheck.isChecked():
+            error = self._is_valid_list_path(self.dstRegexpListsLine)
+            if error:
+                return False, error
+
+            self.rule.operator.type = Config.RULE_TYPE_LISTS
+            self.rule.operator.operand = Config.OPERAND_LIST_DOMAINS_REGEXP
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_LISTS,
+                        'operand': Config.OPERAND_LIST_DOMAINS_REGEXP,
+                        'data': self.dstRegexpListsLine.text(),
+                        'sensitive': self.sensitiveCheck.isChecked()
+                        })
+            self.rule.operator.data = ""
+
+        if self.dstListNetsCheck.isChecked():
+            error = self._is_valid_list_path(self.dstListNetsLine)
+            if error:
+                return False, error
+
+            self.rule.operator.type = Config.RULE_TYPE_LISTS
+            self.rule.operator.operand = Config.OPERAND_LIST_NETS
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_LISTS,
+                        'operand': Config.OPERAND_LIST_NETS,
+                        'data': self.dstListNetsLine.text(),
+                        'sensitive': self.sensitiveCheck.isChecked()
+                        })
+            self.rule.operator.data = ""
+
+
+        if self.dstListIPsCheck.isChecked():
+            error = self._is_valid_list_path(self.dstListIPsLine)
+            if error:
+                return False, error
+
+            self.rule.operator.type = Config.RULE_TYPE_LISTS
+            self.rule.operator.operand = Config.OPERAND_LIST_IPS
+            rule_data.append(
+                    {
+                        'type': Config.RULE_TYPE_LISTS,
+                        'operand': Config.OPERAND_LIST_IPS,
+                        'data': self.dstListIPsLine.text(),
+                        'sensitive': self.sensitiveCheck.isChecked()
+                        })
+            self.rule.operator.data = ""
+
+        if len(rule_data) >= 2:
+            self.rule.operator.type = Config.RULE_TYPE_LIST
+            self.rule.operator.operand = Config.RULE_TYPE_LIST
+            self.rule.operator.data = ""
+            for rd in rule_data:
+                self.rule.operator.list.extend([
+                    ui_pb2.Operator(
+                        type=rd['type'],
+                        operand=rd['operand'],
+                        data=rd['data'],
+                        sensitive=rd['sensitive']
+                    )
+                ])
+                print(self.rule.operator.list)
+
+        elif len(rule_data) == 1:
+            self.rule.operator.operand = rule_data[0]['operand']
+            self.rule.operator.data = rule_data[0]['data']
+            if self.checkProcRegexp.isChecked():
+                self.rule.operator.type = Config.RULE_TYPE_REGEXP
+            elif self.checkCmdlineRegexp.isChecked():
+                self.rule.operator.type = Config.RULE_TYPE_REGEXP
+            elif (self.procCheck.isChecked() == False and self.cmdlineCheck.isChecked() == False) \
+                        and self._is_regex(self.rule.operator.data):
+                    self.rule.operator.type = Config.RULE_TYPE_REGEXP
+
+        else:
+            return False, QC.translate("rules", "Select at least one field.")
+
+        if self.ruleNameEdit.text() == "":
+            self.rule.name = slugify("%s %s %s" % (self.rule.action, self.rule.operator.type, self.rule.operator.data))
+
+        return True, ""
+
+    def edit_rule(self, records, _addr=None):
+        self.WORK_MODE = self.EDIT_RULE
+        self._reset_state()
+
+        self.rule = Rule.new_from_records(records)
+        if self.rule.operator.type not in Config.RulesTypes:
+            Message.ok(QC.translate("rules", "<b>Rule not supported</b>"),
+                       QC.translate("rules", "This type of rule ({0}) is not supported by version {1}".format(self.rule.operator.type, version)),
+                       QtWidgets.QMessageBox.Warning)
+            self.hide()
+            return
+
+        self._old_rule_name = records.value(RuleFields.Name)
+
+        if self._load_rule(addr=_addr, rule=self.rule):
+            self.show()
+
+    def new_rule(self):
+        self.WORK_MODE = self.ADD_RULE
+        self._reset_state()
+        self._load_nodes()
+        self.show()
+
+    def new_rule_from_connection(self, coltime):
+        self.WORK_MODE = self.ADD_RULE
+        self._reset_state()
+        self._load_nodes()
+
+        try:
+            records = self._db.get_connection_by_field("time", coltime)
+            if records.next() == False:
+                print(self.LOG_TAG, "error loading connection fields by time: {0}".format(coltime))
+                return False
+
+            self.set_fields_from_connection(records)
+            self.show()
+        except Exception as e:
+            print(self.LOG_TAG, "exception creating new rule from connection:", e)
+            return False
+
+        return True
diff --git a/ui/opensnitch/dialogs/stats.py b/ui/opensnitch/dialogs/stats.py
new file mode 100644 (file)
index 0000000..26358a7
--- /dev/null
@@ -0,0 +1,2908 @@
+import threading
+import datetime
+import sys
+import os
+import csv
+import io
+
+from PyQt5 import QtCore, QtGui, uic, QtWidgets
+from PyQt5.QtCore import QCoreApplication as QC
+
+from opensnitch.config import Config
+from opensnitch.version import version
+from opensnitch.nodes import Nodes
+from opensnitch.firewall import Firewall, Rules as FwRules
+from opensnitch.dialogs.firewall import FirewallDialog
+from opensnitch.dialogs.preferences import PreferencesDialog
+from opensnitch.dialogs.ruleseditor import RulesEditorDialog
+from opensnitch.dialogs.processdetails import ProcessDetailsDialog
+from opensnitch.dialogs.conndetails import ConnDetails
+from opensnitch.customwidgets.colorizeddelegate import ColorizedDelegate
+from opensnitch.customwidgets.firewalltableview import FirewallTableModel
+from opensnitch.customwidgets.generictableview import GenericTableModel
+from opensnitch.customwidgets.addresstablemodel import AddressTableModel
+from opensnitch.utils import Message, QuickHelp, AsnDB, Icons
+from opensnitch.utils.xdg import xdg_current_desktop
+from opensnitch.actions import Actions
+from opensnitch.rules import Rule, Rules
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+DIALOG_UI_PATH = "%s/../res/stats.ui" % os.path.dirname(sys.modules[__name__].__file__)
+class StatsDialog(QtWidgets.QDialog, uic.loadUiType(DIALOG_UI_PATH)[0]):
+
+    _trigger = QtCore.pyqtSignal(bool, bool)
+    settings_saved = QtCore.pyqtSignal()
+    close_trigger = QtCore.pyqtSignal()
+    _status_changed_trigger = QtCore.pyqtSignal(bool)
+    _shown_trigger = QtCore.pyqtSignal()
+    _notification_trigger = QtCore.pyqtSignal(ui_pb2.Notification)
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+
+    SORT_ORDER = ["ASC", "DESC"]
+    LIMITS = ["LIMIT 50", "LIMIT 100", "LIMIT 200", "LIMIT 300", ""]
+    LAST_GROUP_BY = ""
+
+    # general
+    COL_TIME    = 0
+    COL_NODE    = 1
+    COL_ACTION  = 2
+    COL_SRCPORT = 3
+    COL_SRCIP   = 4
+    COL_DSTIP   = 5
+    COL_DSTHOST = 6
+    COL_DSTPORT = 7
+    COL_PROTO   = 8
+    COL_UID     = 9
+    COL_PID     = 10
+    COL_PROCS   = 11
+    COL_CMDLINE = 12
+    COL_RULES   = 13
+    # total number of columns: cols + 1
+    GENERAL_COL_NUM = 14
+
+    # stats
+    COL_WHAT   = 0
+
+    # rules
+    COL_R_NODE = 1
+    COL_R_NAME = 2
+    COL_R_ENABLED = 3
+    COL_R_ACTION = 4
+    COL_R_DURATION = 5
+    COL_R_OP_TYPE = 6
+    COL_R_OP_OPERAND = 7
+    COL_R_CREATED = 8
+
+    # procs
+    COL_PROC_PID = 11
+
+    TAB_MAIN  = 0
+    TAB_NODES = 1
+    TAB_RULES = 2
+    TAB_HOSTS = 3
+    TAB_PROCS = 4
+    TAB_ADDRS = 5
+    TAB_PORTS = 6
+    TAB_USERS = 7
+    TAB_FIREWALL = 8
+
+    # tree's top level items
+    RULES_TREE_APPS  = 0
+    RULES_TREE_NODES = 1
+    RULES_TREE_FIREWALL = 2
+
+    RULES_TREE_PERMANENT = 0
+    RULES_TREE_TEMPORARY = 1
+
+    RULES_COMBO_PERMANENT = 1
+    RULES_COMBO_TEMPORARY = 2
+    RULES_COMBO_FW = 3
+
+    RULES_TYPE_PERMANENT = 0
+    RULES_TYPE_TEMPORARY = 1
+
+    FILTER_TREE_APPS = 0
+    FILTER_TREE_NODES = 3
+
+    FILTER_TREE_FW_NODE = 0
+    FILTER_TREE_FW_TABLE = 1
+    FILTER_TREE_FW_CHAIN = 2
+
+    # FIXME: don't translate, used only for default argument on _update_status_label
+    FIREWALL_DISABLED = "Disabled"
+
+    # if the user clicks on an item of a table, it'll enter into the detail
+    # view. From there, deny further clicks on the items.
+    IN_DETAIL_VIEW = {
+        TAB_MAIN: False,
+        TAB_NODES: False,
+        TAB_RULES: False,
+        TAB_HOSTS: False,
+        TAB_PROCS: False,
+        TAB_ADDRS: False,
+        TAB_PORTS: False,
+        TAB_USERS: False,
+        TAB_FIREWALL: False
+    }
+    # restore scrollbar position when going back from a detail view
+    LAST_SCROLL_VALUE = None
+    # try to restore last selection
+    LAST_SELECTED_ITEM = ""
+
+    TABLES = {
+        TAB_MAIN: {
+            "name": "connections",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "time as Time, " \
+                    "node, " \
+                    "action, " \
+                    "src_port, " \
+                    "src_ip, " \
+                    "dst_ip, " \
+                    "dst_host, " \
+                    "dst_port, " \
+                    "protocol, " \
+                    "uid, " \
+                    "pid, " \
+                    "process, " \
+                    "process_args, " \
+                    "rule",
+            "group_by": LAST_GROUP_BY,
+            "last_order_by": "1",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        },
+        TAB_NODES: {
+            "name": "nodes",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "last_connection as LastConnection, "\
+                    "addr as Addr, " \
+                    "status as Status, " \
+                    "hostname as Hostname, " \
+                    "daemon_version as Version, " \
+                    "daemon_uptime as Uptime, " \
+                    "daemon_rules as Rules," \
+                    "cons as Connections," \
+                    "cons_dropped as Dropped," \
+                    "version as Version",
+            "header_labels": [],
+            "last_order_by": "1",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        },
+        TAB_RULES: {
+            "name": "rules",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "defaultRulesDelegateConfig",
+            "display_fields": "time as Time," \
+                    "node as Node," \
+                    "name as Name," \
+                    "enabled as Enabled," \
+                    "action as Action," \
+                    "duration as Duration," \
+                    "description as Description, " \
+                    "created as Created",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 0,
+            "tracking_column:": COL_R_NAME
+        },
+        TAB_FIREWALL: {
+            "name": "firewall",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "defaultFWDelegateConfig",
+            "display_fields": "*",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 0,
+            "tracking_column:": COL_TIME
+        },
+        TAB_HOSTS: {
+            "name": "hosts",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "*",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        },
+        TAB_PROCS: {
+            "name": "procs",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "*",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        },
+        TAB_ADDRS: {
+            "name": "addrs",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "*",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        },
+        TAB_PORTS: {
+            "name": "ports",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "*",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        },
+        TAB_USERS: {
+            "name": "users",
+            "label": None,
+            "cmd": None,
+            "cmdCleanStats": None,
+            "view": None,
+            "filterLine": None,
+            "model": None,
+            "delegate": "commonDelegateConfig",
+            "display_fields": "*",
+            "header_labels": [],
+            "last_order_by": "2",
+            "last_order_to": 1,
+            "tracking_column:": COL_TIME
+        }
+    }
+
+    def __init__(self, parent=None, address=None, db=None, dbname="db", appicon=None):
+        super(StatsDialog, self).__init__(parent)
+
+        self.setWindowFlags(QtCore.Qt.Window)
+        self.setupUi(self)
+        self.setWindowIcon(appicon)
+
+        # columns names. Must be added here in order to names be translated.
+        self.COL_STR_NAME = QC.translate("stats", "Name", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_ADDR = QC.translate("stats", "Address", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_STATUS = QC.translate("stats", "Status", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_HOSTNAME = QC.translate("stats", "Hostname", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_UPTIME = QC.translate("stats", "Uptime", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_VERSION = QC.translate("stats", "Version", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_RULES_NUM = QC.translate("stats", "Rules", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_TIME = QC.translate("stats", "Time", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_CREATED = QC.translate("stats", "Created", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_ACTION = QC.translate("stats", "Action", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_DURATION = QC.translate("stats", "Duration", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_DESCRIPTION = QC.translate("stats", "Description", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_NODE = QC.translate("stats", "Node", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_ENABLED = QC.translate("stats", "Enabled", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_PRECEDENCE = QC.translate("stats", "Precedence", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_HITS = QC.translate("stats", "Hits", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_PROTOCOL = QC.translate("stats", "Protocol", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_PROCESS = QC.translate("stats", "Process", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_PROC_CMDLINE = QC.translate("stats", "Cmdline", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_DESTINATION = QC.translate("stats", "Destination", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_SRC_PORT = QC.translate("stats", "SrcPort", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_SRC_IP = QC.translate("stats", "SrcIP", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_DST_IP = QC.translate("stats", "DstIP", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_DST_HOST = QC.translate("stats", "DstHost", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_DST_PORT = QC.translate("stats", "DstPort", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_RULE = QC.translate("stats", "Rule", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_UID = QC.translate("stats", "UserID", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_PID = QC.translate("stats", "PID", "This is a word, without spaces and symbols.").replace(" ", "")
+        self.COL_STR_LAST_CONNECTION = QC.translate("stats", "LastConnection", "This is a word, without spaces and symbols.").replace(" ", "")
+
+        self.FIREWALL_STOPPED  = QC.translate("stats", "Not running")
+        self.FIREWALL_DISABLED = QC.translate("stats", "Disabled")
+        self.FIREWALL_RUNNING  = QC.translate("stats", "Running")
+
+        self._db = db
+        self._db_sqlite = self._db.get_db()
+        self._db_name = dbname
+
+        self.asndb = AsnDB.instance()
+
+        self._cfg = Config.get()
+        self._nodes = Nodes.instance()
+        self._fw = Firewall().instance()
+        self._rules = Rules.instance()
+        self._fw.rules.rulesUpdated.connect(self._cb_fw_rules_updated)
+        self._rules.updated.connect(self._cb_app_rules_updated)
+        self._actions = Actions().instance()
+        self._actions.loadAll()
+        self._last_update = datetime.datetime.now()
+
+        # TODO: allow to display multiples dialogs
+        self._proc_details_dialog = ProcessDetailsDialog(appicon=appicon)
+        # TODO: allow to navigate records by offsets
+        self.prevButton.setVisible(False)
+        self.nextButton.setVisible(False)
+
+
+        self.fwTable.setVisible(False)
+        self.rulesTable.setVisible(True)
+
+        self.daemon_connected = False
+        # skip table updates if a context menu is active
+        self._context_menu_active = False
+        # used to skip updates while the user is moving the scrollbar
+        self.scrollbar_active = False
+
+        self._lock = threading.RLock()
+        self._address = address
+        self._stats = None
+        self._notifications_sent = {}
+
+        self._fw_dialog = FirewallDialog(appicon=appicon)
+        self._prefs_dialog = PreferencesDialog(appicon=appicon)
+        self._rules_dialog = RulesEditorDialog(appicon=appicon)
+        self._prefs_dialog.saved.connect(self._on_settings_saved)
+        self._trigger.connect(self._on_update_triggered)
+        self._notification_callback.connect(self._cb_notification_callback)
+
+        self.nodeLabel.setText("")
+        self.nodeLabel.setStyleSheet('color: green;font-size:12pt; font-weight:600;')
+        self.rulesSplitter.setStretchFactor(0,0)
+        self.rulesSplitter.setStretchFactor(1,2)
+        self.rulesTreePanel.resizeColumnToContents(0)
+        self.rulesTreePanel.resizeColumnToContents(1)
+        self.rulesTreePanel.itemExpanded.connect(self._cb_rules_tree_item_expanded)
+
+        self.startButton.clicked.connect(self._cb_start_clicked)
+        self.nodeStartButton.clicked.connect(self._cb_node_start_clicked)
+        self.nodeStartButton.setVisible(False)
+        self.nodePrefsButton.setVisible(False)
+        self.nodeActionsButton.setVisible(False)
+        self.nodeDeleteButton.setVisible(False)
+        self.nodeDeleteButton.clicked.connect(self._cb_node_delete_clicked)
+        self.prefsButton.clicked.connect(self._cb_prefs_clicked)
+        self.nodePrefsButton.clicked.connect(self._cb_node_prefs_clicked)
+        self.fwButton.clicked.connect(lambda: self._fw_dialog.show())
+        self.comboAction.currentIndexChanged.connect(self._cb_combo_action_changed)
+        self.limitCombo.currentIndexChanged.connect(self._cb_limit_combo_changed)
+        self.tabWidget.currentChanged.connect(self._cb_tab_changed)
+        self.delRuleButton.clicked.connect(self._cb_del_rule_clicked)
+        self.rulesSplitter.splitterMoved.connect(self._cb_rules_splitter_moved)
+        self.rulesTreePanel.itemClicked.connect(self._cb_rules_tree_item_clicked)
+        self.rulesTreePanel.itemDoubleClicked.connect(self._cb_rules_tree_item_double_clicked)
+        self.enableRuleCheck.clicked.connect(self._cb_enable_rule_toggled)
+        self.editRuleButton.clicked.connect(self._cb_edit_rule_clicked)
+        self.newRuleButton.clicked.connect(self._cb_new_rule_clicked)
+        self.cmdProcDetails.clicked.connect(self._cb_proc_details_clicked)
+        self.comboRulesFilter.currentIndexChanged.connect(self._cb_rules_filter_combo_changed)
+        self.helpButton.clicked.connect(self._cb_help_button_clicked)
+        self.nextButton.clicked.connect(self._cb_next_button_clicked)
+        self.prevButton.clicked.connect(self._cb_prev_button_clicked)
+
+        self.enableRuleCheck.setVisible(False)
+        self.delRuleButton.setVisible(False)
+        self.editRuleButton.setVisible(False)
+        self.nodeRuleLabel.setVisible(False)
+        self.comboRulesFilter.setVisible(False)
+
+        menu = QtWidgets.QMenu()
+        menu.addAction(Icons.new(self, "go-up"), QC.translate("stats", "Export rules")).triggered.connect(self._on_menu_node_export_clicked)
+        menu.addAction(Icons.new(self, "go-down"), QC.translate("stats", "Import rules")).triggered.connect(self._on_menu_node_import_clicked)
+        self.nodeActionsButton.setMenu(menu)
+
+        menuActions = QtWidgets.QMenu()
+        menuActions.addAction(Icons.new(self, "go-up"), QC.translate("stats", "Export rules")).triggered.connect(self._on_menu_export_clicked)
+        menuActions.addAction(Icons.new(self, "go-down"), QC.translate("stats", "Import rules")).triggered.connect(self._on_menu_import_clicked)
+        menuActions.addAction(Icons.new(self, "document-save"), QC.translate("stats", "Export events to CSV")).triggered.connect(self._on_menu_export_csv_clicked)
+        menuActions.addAction(Icons.new(self, "application-exit"), QC.translate("stats", "Quit")).triggered.connect(self._on_menu_exit_clicked)
+        self.actionsButton.setMenu(menuActions)
+
+        # translations must be done here, otherwise they don't take effect
+        self.TABLES[self.TAB_NODES]['header_labels'] = [
+            self.COL_STR_LAST_CONNECTION,
+            self.COL_STR_ADDR,
+            self.COL_STR_STATUS,
+            self.COL_STR_HOSTNAME,
+            self.COL_STR_VERSION,
+            self.COL_STR_UPTIME,
+            QC.translate("stats", "Rules", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Connections", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Dropped", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Version", "This is a word, without spaces and symbols.").replace(" ", ""),
+        ]
+
+        self.TABLES[self.TAB_RULES]['header_labels'] = [
+            self.COL_STR_TIME,
+            self.COL_STR_NODE,
+            self.COL_STR_NAME,
+            self.COL_STR_ENABLED,
+            self.COL_STR_ACTION,
+            self.COL_STR_DURATION,
+            self.COL_STR_DESCRIPTION,
+            self.COL_STR_CREATED,
+        ]
+
+        stats_headers = [
+            QC.translate("stats", "What", "This is a word, without spaces and symbols.").replace(" ", ""),
+            QC.translate("stats", "Hits", "This is a word, without spaces and symbols.").replace(" ", ""),
+        ]
+
+        self.TABLES[self.TAB_HOSTS]['header_labels'] = stats_headers
+        self.TABLES[self.TAB_PROCS]['header_labels'] = stats_headers
+        self.TABLES[self.TAB_ADDRS]['header_labels'] = stats_headers
+        self.TABLES[self.TAB_PORTS]['header_labels'] = stats_headers
+        self.TABLES[self.TAB_USERS]['header_labels'] = stats_headers
+
+        self.TABLES[self.TAB_MAIN]['view'] = self._setup_table(QtWidgets.QTableView, self.eventsTable, "connections",
+                self.TABLES[self.TAB_MAIN]['display_fields'],
+                order_by="1",
+                group_by=self.TABLES[self.TAB_MAIN]['group_by'],
+                delegate=self.TABLES[self.TAB_MAIN]['delegate'],
+                resize_cols=(),
+                model=GenericTableModel("connections", [
+                    self.COL_STR_TIME,
+                    self.COL_STR_NODE,
+                    self.COL_STR_ACTION,
+                    self.COL_STR_SRC_PORT,
+                    self.COL_STR_SRC_IP,
+                    self.COL_STR_DST_IP,
+                    self.COL_STR_DST_HOST,
+                    self.COL_STR_DST_PORT,
+                    self.COL_STR_PROTOCOL,
+                    self.COL_STR_UID,
+                    self.COL_STR_PID,
+                    self.COL_STR_PROCESS,
+                    self.COL_STR_PROC_CMDLINE,
+                    self.COL_STR_RULE,
+                ]),
+                verticalScrollBar=self.connectionsTableScrollBar,
+                limit=self._get_limit()
+                )
+        self.TABLES[self.TAB_NODES]['view'] = self._setup_table(QtWidgets.QTableView, self.nodesTable, "nodes",
+                self.TABLES[self.TAB_NODES]['display_fields'],
+                order_by="3,2,1",
+                resize_cols=(self.COL_NODE,),
+                model=GenericTableModel("nodes", self.TABLES[self.TAB_NODES]['header_labels']),
+                verticalScrollBar=self.verticalScrollBar,
+                sort_direction=self.SORT_ORDER[1],
+                delegate=self.TABLES[self.TAB_NODES]['delegate'])
+        self.TABLES[self.TAB_RULES]['view'] = self._setup_table(QtWidgets.QTableView, self.rulesTable, "rules",
+                fields=self.TABLES[self.TAB_RULES]['display_fields'],
+                model=GenericTableModel("rules", self.TABLES[self.TAB_RULES]['header_labels']),
+                verticalScrollBar=self.rulesScrollBar,
+                delegate=self.TABLES[self.TAB_RULES]['delegate'],
+                order_by="2",
+                sort_direction=self.SORT_ORDER[0],
+                tracking_column=self.COL_R_NAME)
+        self.TABLES[self.TAB_FIREWALL]['view'] = self._setup_table(QtWidgets.QTableView,
+                self.fwTable, "firewall",
+                model=FirewallTableModel("firewall"),
+                verticalScrollBar=None,
+                delegate=self.TABLES[self.TAB_FIREWALL]['delegate'],
+                order_by="2",
+                sort_direction=self.SORT_ORDER[0])
+        self.TABLES[self.TAB_HOSTS]['view'] = self._setup_table(QtWidgets.QTableView,
+                self.hostsTable, "hosts",
+                model=GenericTableModel("hosts", self.TABLES[self.TAB_HOSTS]['header_labels']),
+                verticalScrollBar=self.hostsScrollBar,
+                resize_cols=(self.COL_WHAT,),
+                delegate=self.TABLES[self.TAB_HOSTS]['delegate'],
+                order_by="2",
+                limit=self._get_limit()
+                )
+        self.TABLES[self.TAB_PROCS]['view'] = self._setup_table(QtWidgets.QTableView,
+                self.procsTable, "procs",
+                model=GenericTableModel("procs", self.TABLES[self.TAB_PROCS]['header_labels']),
+                verticalScrollBar=self.procsScrollBar,
+                resize_cols=(self.COL_WHAT,),
+                delegate=self.TABLES[self.TAB_PROCS]['delegate'],
+                order_by="2",
+                limit=self._get_limit()
+                )
+        self.TABLES[self.TAB_ADDRS]['view'] = self._setup_table(QtWidgets.QTableView,
+                self.addrTable, "addrs",
+                model=AddressTableModel("addrs", self.TABLES[self.TAB_ADDRS]['header_labels']),
+                verticalScrollBar=self.addrsScrollBar,
+                resize_cols=(self.COL_WHAT,),
+                delegate=self.TABLES[self.TAB_ADDRS]['delegate'],
+                order_by="2",
+                limit=self._get_limit()
+                )
+        self.TABLES[self.TAB_PORTS]['view'] = self._setup_table(QtWidgets.QTableView,
+                self.portsTable, "ports",
+                model=GenericTableModel("ports", self.TABLES[self.TAB_PORTS]['header_labels']),
+                verticalScrollBar=self.portsScrollBar,
+                resize_cols=(self.COL_WHAT,),
+                delegate=self.TABLES[self.TAB_PORTS]['delegate'],
+                order_by="2",
+                limit=self._get_limit()
+                )
+        self.TABLES[self.TAB_USERS]['view'] = self._setup_table(QtWidgets.QTableView,
+                self.usersTable, "users",
+                model=GenericTableModel("users", self.TABLES[self.TAB_USERS]['header_labels']),
+                verticalScrollBar=self.usersScrollBar,
+                resize_cols=(self.COL_WHAT,),
+                delegate=self.TABLES[self.TAB_USERS]['delegate'],
+                order_by="2",
+                limit=self._get_limit()
+                )
+
+        self.TABLES[self.TAB_NODES]['label'] = self.nodesLabel
+        self.TABLES[self.TAB_RULES]['label'] = self.ruleLabel
+        self.TABLES[self.TAB_HOSTS]['label'] = self.hostsLabel
+        self.TABLES[self.TAB_PROCS]['label'] = self.procsLabel
+        self.TABLES[self.TAB_ADDRS]['label'] = self.addrsLabel
+        self.TABLES[self.TAB_PORTS]['label'] = self.portsLabel
+        self.TABLES[self.TAB_USERS]['label'] = self.usersLabel
+
+        self.TABLES[self.TAB_NODES]['cmd'] = self.cmdNodesBack
+        self.TABLES[self.TAB_RULES]['cmd'] = self.cmdRulesBack
+        self.TABLES[self.TAB_HOSTS]['cmd'] = self.cmdHostsBack
+        self.TABLES[self.TAB_PROCS]['cmd'] = self.cmdProcsBack
+        self.TABLES[self.TAB_ADDRS]['cmd'] = self.cmdAddrsBack
+        self.TABLES[self.TAB_PORTS]['cmd'] = self.cmdPortsBack
+        self.TABLES[self.TAB_USERS]['cmd'] = self.cmdUsersBack
+
+        self.TABLES[self.TAB_MAIN]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_NODES]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_RULES]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_HOSTS]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_PROCS]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_ADDRS]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_PORTS]['cmdCleanStats'] = self.cmdCleanSql
+        self.TABLES[self.TAB_USERS]['cmdCleanStats'] = self.cmdCleanSql
+        # the rules clean button is only for a particular rule, not all.
+        self.TABLES[self.TAB_MAIN]['cmdCleanStats'].clicked.connect(lambda: self._cb_clean_sql_clicked(self.TAB_MAIN))
+
+        self.TABLES[self.TAB_MAIN]['filterLine'] = self.filterLine
+        self.TABLES[self.TAB_MAIN]['view'].doubleClicked.connect(self._cb_main_table_double_clicked)
+        self.TABLES[self.TAB_MAIN]['view'].installEventFilter(self)
+        self.TABLES[self.TAB_MAIN]['filterLine'].textChanged.connect(self._cb_events_filter_line_changed)
+
+        self.TABLES[self.TAB_MAIN]['view'].setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+        self.TABLES[self.TAB_MAIN]['view'].customContextMenuRequested.connect(self._cb_table_context_menu)
+        self.TABLES[self.TAB_RULES]['view'].setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+        self.TABLES[self.TAB_RULES]['view'].customContextMenuRequested.connect(self._cb_table_context_menu)
+        self.TABLES[self.TAB_FIREWALL]['view'].setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+        self.TABLES[self.TAB_FIREWALL]['view'].customContextMenuRequested.connect(self._cb_table_context_menu)
+        for idx in range(1,9):
+            if self.TABLES[idx]['cmd'] != None:
+                self.TABLES[idx]['cmd'].hide()
+                self.TABLES[idx]['cmd'].setVisible(False)
+                self.TABLES[idx]['cmd'].clicked.connect(lambda: self._cb_cmd_back_clicked(idx))
+            if self.TABLES[idx]['cmdCleanStats'] != None:
+                self.TABLES[idx]['cmdCleanStats'].clicked.connect(lambda: self._cb_clean_sql_clicked(idx))
+            if self.TABLES[idx]['label'] != None:
+                self.TABLES[idx]['label'].setStyleSheet('font-weight:600;')
+                self.TABLES[idx]['label'].setVisible(False)
+            self.TABLES[idx]['view'].doubleClicked.connect(self._cb_table_double_clicked)
+            self.TABLES[idx]['view'].installEventFilter(self)
+
+        self.TABLES[self.TAB_FIREWALL]['view'].rowsReordered.connect(self._cb_fw_table_rows_reordered)
+
+        self._load_settings()
+
+        self._tables = ( \
+            self.TABLES[self.TAB_MAIN]['view'],
+            self.TABLES[self.TAB_NODES]['view'],
+            self.TABLES[self.TAB_RULES]['view'],
+            self.TABLES[self.TAB_HOSTS]['view'],
+            self.TABLES[self.TAB_PROCS]['view'],
+            self.TABLES[self.TAB_ADDRS]['view'],
+            self.TABLES[self.TAB_PORTS]['view'],
+            self.TABLES[self.TAB_USERS]['view']
+        )
+        self._file_names = ( \
+            'events.csv',
+            'nodes.csv',
+            'rules.csv',
+            'hosts.csv',
+            'procs.csv',
+            'addrs.csv',
+            'ports.csv',
+            'users.csv'
+        )
+
+        self.iconStart = Icons.new(self, "media-playback-start")
+        self.iconPause = Icons.new(self, "media-playback-pause")
+
+        self.fwTreeEdit = QtWidgets.QPushButton()
+        self.fwTreeEdit.setIcon(QtGui.QIcon().fromTheme("preferences-desktop"))
+        self.fwTreeEdit.autoFillBackground = True
+        self.fwTreeEdit.setFlat(True)
+        self.fwTreeEdit.setSizePolicy(
+            QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed)
+        )
+        self.fwTreeEdit.clicked.connect(self._cb_tree_edit_firewall_clicked)
+        self._configure_buttons_icons()
+
+    #Sometimes a maximized window which had been minimized earlier won't unminimize
+    #To workaround, we explicitely maximize such windows when unminimizing happens
+    def changeEvent(self, event):
+        if event.type() == QtCore.QEvent.WindowStateChange:
+            if event.oldState() & QtCore.Qt.WindowMinimized and event.oldState() & QtCore.Qt.WindowMaximized:
+                #a previously minimized maximized window ...
+                if self.windowState() ^ QtCore.Qt.WindowMinimized and xdg_current_desktop == "KDE":
+                    # is not minimized anymore, i.e. it was unminimized
+                    # docs: https://doc.qt.io/qt-5/qwidget.html#setWindowState
+                    self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
+
+    def showEvent(self, event):
+        super(StatsDialog, self).showEvent(event)
+        self._shown_trigger.emit()
+        window_title = QC.translate("stats", "OpenSnitch Network Statistics {0}").format(version)
+        if self._address is not None:
+            window_title = QC.translate("stats", "OpenSnitch Network Statistics for {0}").format(self._address)
+            self.nodeLabel.setText(self._address)
+        self._load_settings()
+        self._add_rulesTree_nodes()
+        self._add_rulesTree_fw_chains()
+        self.setWindowTitle(window_title)
+        self._refresh_active_table()
+        self._show_columns()
+
+    def eventFilter(self, source, event):
+        if event.type() == QtCore.QEvent.KeyPress:
+            if event.matches(QtGui.QKeySequence.Copy):
+                self._copy_selected_rows()
+                return True
+            elif event.key() == QtCore.Qt.Key_Delete:
+                table = self._get_active_table()
+                selection = table.selectionModel().selectedRows()
+                if selection:
+                    model = table.model()
+                    self._table_menu_delete(self.tabWidget.currentIndex(), model, selection)
+                    # we need to manually refresh the model
+                    table.selectionModel().clear()
+                    self._refresh_active_table()
+                return True
+        return super(StatsDialog, self).eventFilter(source, event)
+
+    def _configure_buttons_icons(self):
+        newRuleIcon = Icons.new(self, "document-new")
+        delRuleIcon = Icons.new(self, "edit-delete")
+        editRuleIcon = Icons.new(self, "accessories-text-editor")
+        prefsIcon = Icons.new(self, "preferences-system")
+        searchIcon = Icons.new(self, "system-search")
+        clearIcon = Icons.new(self, "edit-clear-all")
+        leftArrowIcon = Icons.new(self, "go-previous")
+        fwIcon = Icons.new(self, "security-high")
+        optsIcon = Icons.new(self, "format-justify-fill")
+        helpIcon = Icons.new(self, "help-browser")
+        eventsIcon = Icons.new(self, "view-sort-ascending")
+        rulesIcon = Icons.new(self, "address-book-new")
+        procsIcon = Icons.new(self, "system-run")
+
+        if QtGui.QIcon().hasThemeIcon("preferences-desktop") == False:
+            self.fwTreeEdit.setText("+")
+
+        self.tabWidget.setTabIcon(self.TAB_MAIN, eventsIcon)
+        self.tabWidget.setTabIcon(self.TAB_RULES, rulesIcon)
+        self.tabWidget.setTabIcon(self.TAB_PROCS, procsIcon)
+        self.newRuleButton.setIcon(newRuleIcon)
+        self.delRuleButton.setIcon(delRuleIcon)
+        self.editRuleButton.setIcon(editRuleIcon)
+        self.prefsButton.setIcon(prefsIcon)
+        self.helpButton.setIcon(helpIcon)
+        self.startButton.setIcon(self.iconStart)
+        self.fwButton.setIcon(fwIcon)
+        self.cmdProcDetails.setIcon(searchIcon)
+        self.nodeStartButton.setIcon(self.iconStart)
+        self.nodePrefsButton.setIcon(prefsIcon)
+        self.nodeDeleteButton.setIcon(clearIcon)
+        self.nodeActionsButton.setIcon(optsIcon)
+        self.actionsButton.setIcon(optsIcon)
+        self.TABLES[self.TAB_MAIN]['cmdCleanStats'].setIcon(clearIcon)
+        for idx in range(1,8):
+            self.TABLES[idx]['cmd'].setIcon(leftArrowIcon)
+            if self.TABLES[idx]['cmdCleanStats'] != None:
+                self.TABLES[idx]['cmdCleanStats'].setIcon(clearIcon)
+
+    def _load_settings(self):
+        self._ui_refresh_interval = self._cfg.getInt(Config.STATS_REFRESH_INTERVAL, 0)
+        dialog_geometry = self._cfg.getSettings(Config.STATS_GEOMETRY)
+        dialog_last_tab = self._cfg.getSettings(Config.STATS_LAST_TAB)
+        dialog_general_filter_text = self._cfg.getSettings(Config.STATS_FILTER_TEXT)
+        dialog_general_filter_action = self._cfg.getSettings(Config.STATS_FILTER_ACTION)
+        dialog_general_limit_results = self._cfg.getSettings(Config.STATS_LIMIT_RESULTS)
+        if dialog_geometry != None:
+            self.restoreGeometry(dialog_geometry)
+        if dialog_last_tab != None:
+            self.tabWidget.setCurrentIndex(int(dialog_last_tab))
+        if dialog_general_filter_action != None:
+            self.comboAction.setCurrentIndex(int(dialog_general_filter_action))
+        if dialog_general_limit_results != None:
+            # XXX: a little hack, because if the saved index is 0, the signal is not fired.
+            # XXX: this causes to fire the event twice
+            self.limitCombo.blockSignals(True);
+            self.limitCombo.setCurrentIndex(4)
+            self.limitCombo.setCurrentIndex(int(dialog_general_limit_results))
+            self.limitCombo.blockSignals(False);
+
+        rules_splitter_pos = self._cfg.getSettings(Config.STATS_RULES_SPLITTER_POS)
+        if type(rules_splitter_pos) == QtCore.QByteArray:
+            self.rulesSplitter.restoreState(rules_splitter_pos)
+            rulesSizes = self.rulesSplitter.sizes()
+            if self.IN_DETAIL_VIEW[self.TAB_RULES] == True:
+                self.comboRulesFilter.setVisible(False)
+            elif len(rulesSizes) > 0:
+                self.comboRulesFilter.setVisible(rulesSizes[0] == 0)
+        else:
+            w = self.rulesSplitter.width()
+            self.rulesSplitter.setSizes([int(w/3), int(w/2)])
+
+        self._restore_details_view_columns(self.eventsTable.horizontalHeader(), Config.STATS_GENERAL_COL_STATE)
+        self._restore_details_view_columns(self.nodesTable.horizontalHeader(), Config.STATS_NODES_COL_STATE)
+        self._restore_details_view_columns(self.rulesTable.horizontalHeader(), Config.STATS_RULES_COL_STATE)
+        self._restore_details_view_columns(self.fwTable.horizontalHeader(), Config.STATS_FW_COL_STATE)
+
+        rulesTreeNodes_expanded = self._cfg.getBool(Config.STATS_RULES_TREE_EXPANDED_1)
+        if rulesTreeNodes_expanded != None:
+            rules_tree_nodes = self._get_rulesTree_item(self.RULES_TREE_NODES)
+            if rules_tree_nodes != None:
+                rules_tree_nodes.setExpanded(rulesTreeNodes_expanded)
+        rulesTreeApps_expanded = self._cfg.getBool(Config.STATS_RULES_TREE_EXPANDED_0)
+        if rulesTreeApps_expanded != None:
+            rules_tree_apps = self._get_rulesTree_item(self.RULES_TREE_APPS)
+            if rules_tree_apps != None:
+                rules_tree_apps.setExpanded(rulesTreeApps_expanded)
+
+        if dialog_general_filter_text != None:
+            self.filterLine.setText(dialog_general_filter_text)
+
+    def _save_settings(self):
+        self._cfg.setSettings(Config.STATS_GEOMETRY, self.saveGeometry())
+        self._cfg.setSettings(Config.STATS_LAST_TAB, self.tabWidget.currentIndex())
+        self._cfg.setSettings(Config.STATS_LIMIT_RESULTS, self.limitCombo.currentIndex())
+        self._cfg.setSettings(Config.STATS_FILTER_TEXT, self.filterLine.text())
+
+        header = self.eventsTable.horizontalHeader()
+        self._cfg.setSettings(Config.STATS_GENERAL_COL_STATE, header.saveState())
+        nodesHeader = self.nodesTable.horizontalHeader()
+        self._cfg.setSettings(Config.STATS_NODES_COL_STATE, nodesHeader.saveState())
+        rulesHeader = self.rulesTable.horizontalHeader()
+        self._cfg.setSettings(Config.STATS_RULES_COL_STATE, rulesHeader.saveState())
+        fwHeader = self.fwTable.horizontalHeader()
+        self._cfg.setSettings(Config.STATS_FW_COL_STATE, fwHeader.saveState())
+
+        rules_tree_apps = self._get_rulesTree_item(self.RULES_TREE_APPS)
+        if rules_tree_apps != None:
+            self._cfg.setSettings(Config.STATS_RULES_TREE_EXPANDED_0, rules_tree_apps.isExpanded())
+        rules_tree_nodes = self._get_rulesTree_item(self.RULES_TREE_NODES)
+        if rules_tree_nodes != None:
+            self._cfg.setSettings(Config.STATS_RULES_TREE_EXPANDED_1, rules_tree_nodes.isExpanded())
+
+    def _del_by_field(self, cur_idx, table, value):
+        model = self._get_active_table().model()
+        # get left side of the query: * GROUP BY ...
+        qstr = model.query().lastQuery().split("GROUP BY")[0]
+        # get right side of the query: ... WHERE *
+        q = qstr.split("WHERE")
+
+        field = "dst_host"
+        if cur_idx == self.TAB_NODES:
+            field = "node"
+        elif cur_idx == self.TAB_PROCS:
+            field = "process"
+        elif cur_idx == self.TAB_ADDRS:
+            field = "dst_ip"
+        elif cur_idx == self.TAB_PORTS:
+            field = "dst_port"
+        elif cur_idx == self.TAB_USERS:
+            field = "uid"
+
+        ret1 = self._db.remove("DELETE FROM {0} WHERE what = '{1}'".format(table, value))
+        ret2 = self._db.remove("DELETE FROM connections WHERE {0} = '{1}'".format(field, value))
+
+        return ret1 and ret2
+
+    def _del_rule(self, rule_name, node_addr):
+        if rule_name == None or node_addr == None:
+            print("_del_rule() invalid parameters")
+            return
+        nid, noti = self._nodes.delete_rule(rule_name, node_addr, self._notification_callback)
+        if nid == None:
+            return
+        self._notifications_sent[nid] = noti
+
+    # https://stackoverflow.com/questions/40225270/copy-paste-multiple-items-from-qtableview-in-pyqt4
+    def _copy_selected_rows(self):
+        cur_idx = self.tabWidget.currentIndex()
+        if self.tabWidget.currentIndex() == self.TAB_RULES and self.fwTable.isVisible():
+            cur_idx = self.TAB_FIREWALL
+        elif self.tabWidget.currentIndex() == self.TAB_RULES and not self.fwTable.isVisible():
+            cur_idx = self.TAB_RULES
+
+        selection = self.TABLES[cur_idx]['view'].copySelection()
+        if selection:
+            stream = io.StringIO()
+            csv.writer(stream, delimiter=',').writerows(selection)
+            QtWidgets.qApp.clipboard().setText(stream.getvalue())
+
+    def _configure_events_contextual_menu(self, pos):
+        try:
+            cur_idx = self.tabWidget.currentIndex()
+            table = self._get_active_table()
+            model = table.model()
+
+            selection = table.selectionModel().selectedRows()
+            if not selection:
+                return False
+
+            menu = QtWidgets.QMenu()
+            _menu_details = menu.addAction(QC.translate("stats", "Details"))
+            rulesMenu = QtWidgets.QMenu(QC.translate("stats", "Rules"))
+            _menu_new_rule = rulesMenu.addAction(QC.translate("stats", "New"))
+            menu.addMenu(rulesMenu)
+
+            # move away menu a few pixels to the right, to avoid clicking on it by mistake
+            point = QtCore.QPoint(pos.x()+10, pos.y()+5)
+            action = menu.exec_(table.mapToGlobal(point))
+
+            model = table.model()
+
+            if action == _menu_new_rule:
+                self._table_menu_new_rule_from_row(cur_idx, model, selection)
+            elif action == _menu_details:
+                coltime = model.index(selection[0].row(), self.COL_TIME).data()
+                o = ConnDetails(self)
+                o.showByField("time", coltime)
+
+        except Exception as e:
+            print(e)
+        finally:
+            self._clear_rows_selection()
+            return True
+
+    def _configure_fwrules_contextual_menu(self, pos):
+        try:
+            cur_idx = self.tabWidget.currentIndex()
+            table = self._get_active_table()
+            model = table.model()
+            menu = QtWidgets.QMenu()
+            exportMenu = QtWidgets.QMenu(QC.translate("stats", "Export"))
+
+            selection = table.selectionModel().selectedRows()
+            if not selection:
+                return False
+            is_rule_enabled = model.index(selection[0].row(), FirewallTableModel.COL_ENABLED).data()
+            rule_action = model.index(selection[0].row(), FirewallTableModel.COL_ACTION).data()
+            rule_action = rule_action.lower()
+
+            if rule_action == Config.ACTION_ACCEPT or \
+                    rule_action == Config.ACTION_DROP or \
+                    rule_action == Config.ACTION_RETURN or \
+                    rule_action == Config.ACTION_REJECT:
+                actionsMenu = QtWidgets.QMenu(QC.translate("stats", "Action"))
+                _action_accept = actionsMenu.addAction(Config.ACTION_ACCEPT)
+                _action_drop = actionsMenu.addAction(Config.ACTION_DROP)
+                _action_reject = actionsMenu.addAction(Config.ACTION_REJECT)
+                _action_return = actionsMenu.addAction(Config.ACTION_RETURN)
+                menu.addSeparator()
+                menu.addMenu(actionsMenu)
+
+            _label_enable = QC.translate("stats", "Disable")
+            if is_rule_enabled == "False":
+                _label_enable = QC.translate("stats", "Enable")
+            _menu_enable = menu.addAction(_label_enable)
+            _menu_delete = menu.addAction(QC.translate("stats", "Delete"))
+            _menu_edit = menu.addAction(QC.translate("stats", "Edit"))
+
+            menu.addSeparator()
+            _toClipboard = exportMenu.addAction(QC.translate("stats", "To clipboard"))
+            #_toDisk = exportMenu.addAction(QC.translate("stats", "To disk"))
+            menu.addMenu(exportMenu)
+
+            # move away menu a few pixels to the right, to avoid clicking on it by mistake
+            point = QtCore.QPoint(pos.x()+10, pos.y()+5)
+            action = menu.exec_(table.mapToGlobal(point))
+
+            model = table.model()
+
+            # block fw rules signals, to prevent reloading them per operation,
+            # which can lead to race conditions.
+            self._fw.rules.blockSignals(True)
+            if action == _menu_delete:
+                self._table_menu_delete(cur_idx, model, selection)
+            elif action == _menu_enable:
+                self._table_menu_enable(cur_idx, model, selection, is_rule_enabled)
+            elif action == _menu_edit:
+                self._table_menu_edit(cur_idx, model, selection)
+            elif action == _action_accept or \
+                action == _action_drop or \
+                action == _action_reject or \
+                action == _action_return:
+                self._table_menu_change_rule_field(cur_idx, model, selection, FwRules.FIELD_TARGET, action.text())
+            elif action == _toClipboard:
+                self._table_menu_export_clipboard(cur_idx, model, selection)
+            #elif action == _toDisk:
+            #    self._table_menu_export_disk(cur_idx, model, selection)
+
+            self._fw.rules.blockSignals(False)
+
+        except Exception as e:
+            print("fwrules contextual menu error:", e)
+        finally:
+            self._clear_rows_selection()
+            return True
+
+    def _configure_rules_contextual_menu(self, pos):
+        try:
+            cur_idx = self.tabWidget.currentIndex()
+            table = self._get_active_table()
+            model = table.model()
+
+            selection = table.selectionModel().selectedRows()
+            if not selection:
+                return False
+
+            menu = QtWidgets.QMenu()
+            durMenu = QtWidgets.QMenu(self.COL_STR_DURATION)
+            actionMenu = QtWidgets.QMenu(self.COL_STR_ACTION)
+            nodesMenu = QtWidgets.QMenu(QC.translate("stats", "Apply to"))
+            exportMenu = QtWidgets.QMenu(QC.translate("stats", "Export"))
+
+            nodes_menu = []
+            if self._nodes.count() > 0:
+                for node in self._nodes.get_nodes():
+                    nodes_menu.append([nodesMenu.addAction(node), node])
+                menu.addMenu(nodesMenu)
+
+            _actAllow = actionMenu.addAction(QC.translate("stats", "Allow"))
+            _actDeny = actionMenu.addAction(QC.translate("stats", "Deny"))
+            _actReject = actionMenu.addAction(QC.translate("stats", "Reject"))
+            menu.addMenu(actionMenu)
+
+            _durAlways = durMenu.addAction(QC.translate("stats", "Always"))
+            _durUntilReboot = durMenu.addAction(QC.translate("stats", "Until reboot"))
+            _dur1h = durMenu.addAction(Config.DURATION_1h)
+            _dur30m = durMenu.addAction(Config.DURATION_30m)
+            _dur15m = durMenu.addAction(Config.DURATION_15m)
+            _dur5m = durMenu.addAction(Config.DURATION_5m)
+            menu.addMenu(durMenu)
+
+            is_rule_enabled = model.index(selection[0].row(), self.COL_R_ENABLED).data()
+            menu_label_enable = QC.translate("stats", "Disable")
+            if is_rule_enabled == "False":
+                menu_label_enable = QC.translate("stats", "Enable")
+
+            _menu_enable = menu.addAction(QC.translate("stats", menu_label_enable))
+            _menu_duplicate = menu.addAction(QC.translate("stats", "Duplicate"))
+            _menu_edit = menu.addAction(QC.translate("stats", "Edit"))
+            _menu_delete = menu.addAction(QC.translate("stats", "Delete"))
+
+            menu.addSeparator()
+            _toClipboard = exportMenu.addAction(QC.translate("stats", "To clipboard"))
+            _toDisk = exportMenu.addAction(QC.translate("stats", "To disk"))
+            menu.addMenu(exportMenu)
+
+            # move away menu a few pixels to the right, to avoid clicking on it by mistake
+            point = QtCore.QPoint(pos.x()+10, pos.y()+5)
+            action = menu.exec_(table.mapToGlobal(point))
+
+            model = table.model()
+
+            if self._nodes.count() > 0:
+                for nmenu in nodes_menu:
+                    node_action = nmenu[0]
+                    node_addr = nmenu[1]
+                    if action == node_action:
+                        ret = Message.yes_no(
+                            QC.translate("stats", "    Apply this rule to {0}  ".format(node_addr)),
+                            QC.translate("stats", "    Are you sure?"),
+                            QtWidgets.QMessageBox.Warning)
+                        if ret == QtWidgets.QMessageBox.Cancel:
+                            return False
+                        self._table_menu_apply_to_node(cur_idx, model, selection, node_addr)
+                        return False
+
+            if action == _menu_delete:
+                self._table_menu_delete(cur_idx, model, selection)
+            elif action == _menu_edit:
+                self._table_menu_edit(cur_idx, model, selection)
+            elif action == _menu_enable:
+                self._table_menu_enable(cur_idx, model, selection, is_rule_enabled)
+            elif action == _menu_duplicate:
+                self._table_menu_duplicate(cur_idx, model, selection)
+            elif action == _durAlways:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_ALWAYS)
+            elif action == _dur1h:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_1h)
+            elif action == _dur30m:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_30m)
+            elif action == _dur15m:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_15m)
+            elif action == _dur5m:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_5m)
+            elif action == _durUntilReboot:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "duration", Config.DURATION_UNTIL_RESTART)
+            elif action == _actAllow:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "action", Config.ACTION_ALLOW)
+            elif action == _actDeny:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "action", Config.ACTION_DENY)
+            elif action == _actReject:
+                self._table_menu_change_rule_field(cur_idx, model, selection, "action", Config.ACTION_REJECT)
+            elif action == _toClipboard:
+                self._table_menu_export_clipboard(cur_idx, model, selection)
+            elif action == _toDisk:
+                self._table_menu_export_disk(cur_idx, model, selection)
+
+        except Exception as e:
+            print("rules contextual menu exception:", e)
+        finally:
+            self._clear_rows_selection()
+            return True
+
+    def _table_menu_export_clipboard(self, cur_idx, model, selection):
+        rules_list = []
+        if cur_idx == self.TAB_RULES and not self.fwTable.isVisible():
+            for idx in selection:
+                rule_name = model.index(idx.row(), self.COL_R_NAME).data()
+                node_addr = model.index(idx.row(), self.COL_R_NODE).data()
+
+                json_rule = self._nodes.rule_to_json(node_addr, rule_name)
+                if json_rule != None:
+                    rules_list.append(json_rule)
+                else:
+                    print("export to clipboard: ERROR converting \"{0}\" to json".format(rule_name))
+        elif cur_idx == self.TAB_RULES and self.fwTable.isVisible():
+            for idx in selection:
+                uuid = model.index(idx.row(), FirewallTableModel.COL_UUID).data()
+                node = model.index(idx.row(), FirewallTableModel.COL_ADDR).data()
+                r = self._fw.get_protorule_by_uuid(node, uuid)
+                if r:
+                    rules_list.append(self._fw.rule_to_json(r))
+
+        cliptext=""
+        for r in rules_list:
+            cliptext = "{0}\n{1}".format(cliptext, r)
+
+        QtWidgets.qApp.clipboard().setText(cliptext)
+
+    def _table_menu_export_disk(self, cur_idx, model, selection):
+        outdir = QtWidgets.QFileDialog.getExistingDirectory(self,
+                                                            os.path.expanduser("~"),
+                                                            QC.translate("stats", 'Select a directory to export rules'),
+                                                            QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks)
+        if outdir == "":
+            return
+
+        error_list = []
+        for idx in selection:
+            rule_name = model.index(idx.row(), self.COL_R_NAME).data()
+            node_addr = model.index(idx.row(), self.COL_R_NODE).data()
+
+            ok = self._nodes.export_rule(node_addr, rule_name, outdir)
+            if not ok:
+                error_list.append(rule_name)
+
+        if len(error_list) == 0:
+            Message.ok("Rules export",
+                       QC.translate("stats", "Rules exported to {0}".format(outdir)),
+                       QtWidgets.QMessageBox.Information)
+        else:
+            error_text = ""
+            for e in error_list:
+                error_text = "{0}<br>{1}".format(error_text, e)
+
+            Message.ok("Rules export error",
+                        QC.translate("stats",
+                                     "Error exporting the following rules:<br><br>".format(error_text)
+                                    ),
+                        QtWidgets.QMessageBox.Warning)
+
+    def _table_menu_duplicate(self, cur_idx, model, selection):
+
+        for idx in selection:
+            rule_name = model.index(idx.row(), self.COL_R_NAME).data()
+            node_addr = model.index(idx.row(), self.COL_R_NODE).data()
+
+            records = None
+            for idx in range(0,100):
+                records = self._get_rule(rule_name, node_addr)
+                if records == None or records.size() == -1:
+                    rule = Rule.new_from_records(records)
+                    rule.name = "cloned-{0}-{1}".format(idx, rule.name)
+                    self._rules.add_rules(node_addr, [rule])
+                    break
+
+            if records != None and records.size() == -1:
+                noti = ui_pb2.Notification(type=ui_pb2.CHANGE_RULE, rules=[rule])
+                nid = self._nodes.send_notification(node_addr, noti, self._notification_callback)
+                if nid != None:
+                    self._notifications_sent[nid] = noti
+
+    def _table_menu_apply_to_node(self, cur_idx, model, selection, node_addr):
+
+        for idx in selection:
+            rule_name = model.index(idx.row(), self.COL_R_NAME).data()
+            records = self._get_rule(rule_name, None)
+            rule = Rule.new_from_records(records)
+
+            noti = ui_pb2.Notification(type=ui_pb2.CHANGE_RULE, rules=[rule])
+            nid = self._nodes.send_notification(node_addr, noti, self._notification_callback)
+            if nid != None:
+                self._rules.add_rules(node_addr, [rule])
+                self._notifications_sent[nid] = noti
+
+    def _table_menu_change_rule_field(self, cur_idx, model, selection, field, value):
+        if cur_idx == self.TAB_RULES and not self.fwTable.isVisible():
+            for idx in selection:
+                rule_name = model.index(idx.row(), self.COL_R_NAME).data()
+                node_addr = model.index(idx.row(), self.COL_R_NODE).data()
+
+                records = self._get_rule(rule_name, node_addr)
+                rule = Rule.new_from_records(records)
+
+                self._db.update(table="rules", fields="{0}=?".format(field),
+                                values=[value], condition="name='{0}' AND node='{1}'".format(rule_name, node_addr),
+                                action_on_conflict="")
+
+                if field == "action":
+                    rule.action = value
+                elif field == "duration":
+                    rule.duration = value
+                elif field == "precedence":
+                    rule.precedence = value
+
+                noti = ui_pb2.Notification(type=ui_pb2.CHANGE_RULE, rules=[rule])
+                nid = self._nodes.send_notification(node_addr, noti, self._notification_callback)
+                if nid != None:
+                    self._notifications_sent[nid] = noti
+        elif cur_idx == self.TAB_RULES and self.fwTable.isVisible():
+            nodes_updated = []
+            for idx in selection:
+                uuid = model.index(idx.row(), FirewallTableModel.COL_UUID).data()
+                node = model.index(idx.row(), FirewallTableModel.COL_ADDR).data()
+                updated, err = self._fw.change_rule_field(node, uuid, field, value)
+                if updated:
+                    nodes_updated.append(node)
+                else:
+                    print("error updating fw rule field", field, "value:", value)
+
+            for addr in nodes_updated:
+                node = self._nodes.get_node(addr)
+                nid, noti = self._nodes.reload_fw(addr, node['firewall'], self._notification_callback)
+                self._notifications_sent[nid] = noti
+
+    def _table_menu_enable(self, cur_idx, model, selection, is_rule_enabled):
+        rule_status = "False" if is_rule_enabled == "True" else "True"
+        enable_rule = False if is_rule_enabled == "True" else True
+
+        if cur_idx == self.TAB_RULES and not self.fwTable.isVisible():
+            for idx in selection:
+                rule_name = model.index(idx.row(), self.COL_R_NAME).data()
+                node_addr = model.index(idx.row(), self.COL_R_NODE).data()
+
+                records = self._get_rule(rule_name, node_addr)
+                rule = Rule.new_from_records(records)
+                rule_type = ui_pb2.DISABLE_RULE if is_rule_enabled == "True" else ui_pb2.ENABLE_RULE
+
+                self._db.update(table="rules", fields="enabled=?",
+                                values=[rule_status], condition="name='{0}' AND node='{1}'".format(rule_name, node_addr),
+                                action_on_conflict="")
+
+                noti = ui_pb2.Notification(type=rule_type, rules=[rule])
+                nid = self._nodes.send_notification(node_addr, noti, self._notification_callback)
+                if nid != None:
+                    self._notifications_sent[nid] = noti
+
+        elif cur_idx == self.TAB_RULES and self.fwTable.isVisible():
+            nodes_updated = []
+            for idx in selection:
+                uuid = model.index(idx.row(), FirewallTableModel.COL_UUID).data()
+                node = model.index(idx.row(), FirewallTableModel.COL_ADDR).data()
+                updated, err = self._fw.enable_rule(node, uuid, enable_rule)
+                if updated:
+                    nodes_updated.append(node)
+
+            for addr in nodes_updated:
+                node = self._nodes.get_node(addr)
+                nid, noti = self._nodes.reload_fw(addr, node['firewall'], self._notification_callback)
+                self._notifications_sent[nid] = noti
+
+    def _table_menu_delete(self, cur_idx, model, selection):
+        if cur_idx == self.TAB_MAIN or cur_idx == self.TAB_NODES or self.IN_DETAIL_VIEW[cur_idx]:
+            return
+
+        msg = QC.translate("stats", "    You are about to delete this rule.    ")
+        if cur_idx != self.TAB_RULES:
+            msg = QC.translate("stats", "    You are about to delete this entry.    ")
+
+        ret = Message.yes_no(msg,
+            QC.translate("stats", "    Are you sure?"),
+            QtWidgets.QMessageBox.Warning)
+        if ret == QtWidgets.QMessageBox.Cancel:
+            return False
+
+        if cur_idx == self.TAB_RULES and self.fwTable.isVisible():
+            nodes_updated = {}
+            for idx in selection:
+                uuid = model.index(idx.row(), FirewallTableModel.COL_UUID).data()
+                node = model.index(idx.row(), FirewallTableModel.COL_ADDR).data()
+                ok, fw_config = self._fw.delete_rule(node, uuid)
+                if ok:
+                    nodes_updated[node] = fw_config
+                else:
+                    print("error deleting fw rule:", uuid, "row:", idx.row())
+
+            for addr in nodes_updated:
+                nid, noti = self._nodes.reload_fw(addr, nodes_updated[addr], self._notification_callback)
+                self._notifications_sent[nid] = noti
+
+        elif cur_idx == self.TAB_RULES and not self.fwTable.isVisible():
+            for idx in selection:
+                name = model.index(idx.row(), self.COL_R_NAME).data()
+                node = model.index(idx.row(), self.COL_R_NODE).data()
+                self._del_rule(name, node)
+            self._refresh_active_table()
+
+        elif cur_idx == self.TAB_HOSTS or cur_idx == self.TAB_PROCS or cur_idx == self.TAB_ADDRS or \
+            cur_idx == self.TAB_USERS or cur_idx == self.TAB_PORTS:
+            do_refresh = False
+            for idx in selection:
+                field = model.index(idx.row(), self.COL_WHAT).data()
+                if field == "":
+                    continue
+                ok = self._del_by_field(cur_idx, self.TABLES[cur_idx]['name'], field)
+                do_refresh |= ok
+            if do_refresh:
+                self._refresh_active_table()
+
+    def _table_menu_new_rule_from_row(self, cur_idx, model, selection):
+        coltime = model.index(selection[0].row(), self.COL_TIME).data()
+        if self._rules_dialog.new_rule_from_connection(coltime) == False:
+
+            Message.ok(QC.translate("stats", "New rule error"),
+                        QC.translate("stats",
+                                    "Error creating new rule from event ({0})".format(coltime)
+                                    ),
+                        QtWidgets.QMessageBox.Warning)
+
+    def _table_menu_edit(self, cur_idx, model, selection):
+        if cur_idx == self.TAB_RULES and not self.fwTable.isVisible():
+            for idx in selection:
+                name = model.index(idx.row(), self.COL_R_NAME).data()
+                node = model.index(idx.row(), self.COL_R_NODE).data()
+                records = self._get_rule(name, node)
+                if records == None or records == -1:
+                    Message.ok(QC.translate("stats", "New rule error"),
+                            QC.translate("stats", "Rule not found by that name and node"),
+                            QtWidgets.QMessageBox.Warning)
+                    return
+                self._rules_dialog.edit_rule(records, node)
+                break
+
+        elif cur_idx == self.TAB_RULES and self.fwTable.isVisible():
+            for idx in selection:
+                uuid = model.index(idx.row(), FirewallTableModel.COL_UUID).data()
+                node = model.index(idx.row(), FirewallTableModel.COL_ADDR).data()
+                self._fw_dialog.load_rule(node, uuid)
+
+                break
+
+    def _cb_fw_rules_updated(self):
+        self._add_rulesTree_fw_chains()
+
+    def _cb_app_rules_updated(self, what):
+        self._refresh_active_table()
+
+    @QtCore.pyqtSlot(str)
+    def _cb_fw_table_rows_reordered(self, node_addr):
+        node = self._nodes.get_node(node_addr)
+        nid, notif = self._nodes.reload_fw(node_addr, node['firewall'], self._notification_callback)
+        self._notifications_sent[nid] = {'addr': node_addr, 'notif': notif}
+
+    # ignore updates while the user is using the scrollbar.
+    def _cb_scrollbar_pressed(self):
+        self.scrollbar_active = True
+
+    def _cb_scrollbar_released(self):
+        self.scrollbar_active = False
+
+    def _cb_tree_edit_firewall_clicked(self):
+        self._fw_dialog.show()
+
+    def _cb_proc_details_clicked(self):
+        table = self._tables[self.tabWidget.currentIndex()]
+        nrows = table.model().rowCount()
+        pids = {}
+        for row in range(0, nrows):
+            pid = table.model().index(row, self.COL_PROC_PID).data()
+            node = table.model().index(row, self.COL_NODE).data()
+            if pid not in pids:
+                pids[pid] = node
+
+        self._proc_details_dialog.monitor(pids)
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def _cb_notification_callback(self, reply):
+        if reply.id in self._notifications_sent:
+            if reply.code == ui_pb2.ERROR:
+                Message.ok(
+                    QC.translate("stats", "Error:"),
+                    "{0}".format(reply.data),
+                    QtWidgets.QMessageBox.Warning)
+
+            del self._notifications_sent[reply.id]
+
+        else:
+            Message.ok(
+                QC.translate("stats", "Warning:"),
+                "{0}".format(reply.data),
+                QtWidgets.QMessageBox.Warning)
+
+    def _cb_tab_changed(self, index):
+        self.comboAction.setVisible(index == self.TAB_MAIN)
+
+        self.TABLES[index]['cmdCleanStats'].setVisible(True)
+        if index == self.TAB_MAIN:
+            self._set_events_query()
+        else:
+            if index == self.TAB_RULES:
+                # display the clean buton only if not in detail view
+                self.TABLES[index]['cmdCleanStats'].setVisible( self.IN_DETAIL_VIEW[index] )
+                self._add_rulesTree_nodes()
+
+            elif index == self.TAB_PROCS:
+                # make the button visible depending if we're in the detail view
+                nrows = self._get_active_table().model().rowCount()
+                self.cmdProcDetails.setVisible(self.IN_DETAIL_VIEW[index] and nrows > 0)
+            elif index == self.TAB_NODES:
+                self.TABLES[index]['cmdCleanStats'].setVisible( self.IN_DETAIL_VIEW[index] )
+
+        self._refresh_active_table()
+
+    def _cb_table_context_menu(self, pos):
+        cur_idx = self.tabWidget.currentIndex()
+        if cur_idx != self.TAB_RULES and cur_idx != self.TAB_MAIN:
+            # the only tables with context menu for now are events and rules table
+            return
+        if self.IN_DETAIL_VIEW[self.TAB_RULES] == True:
+            return
+
+        self._context_menu_active = True
+        if cur_idx == self.TAB_MAIN:
+            refresh_table = self._configure_events_contextual_menu(pos)
+        elif cur_idx == self.TAB_RULES:
+            if self.fwTable.isVisible():
+                refresh_table = self._configure_fwrules_contextual_menu(pos)
+            else:
+                refresh_table = self._configure_rules_contextual_menu(pos)
+
+        self._context_menu_active = False
+        if refresh_table:
+            self._refresh_active_table()
+
+
+    def _cb_table_header_clicked(self, pos, sortIdx):
+        cur_idx = self.tabWidget.currentIndex()
+        # TODO: allow ordering by Network column
+        if cur_idx == self.TAB_ADDRS and pos == 2:
+            return
+
+        model = self._get_active_table().model()
+        qstr = model.query().lastQuery().split("ORDER BY")[0]
+
+        q = qstr.strip(" ") + " ORDER BY %d %s" % (pos+1, self.SORT_ORDER[sortIdx])
+        if cur_idx > 0 and self.TABLES[cur_idx]['cmd'].isVisible() == False:
+            self.TABLES[cur_idx]['last_order_by'] = pos+1
+            self.TABLES[cur_idx]['last_order_to'] = sortIdx
+
+            q = qstr.strip(" ") + self._get_order()
+
+        q += self._get_limit()
+        self.setQuery(model, q)
+
+    def _cb_events_filter_line_changed(self, text):
+        cur_idx = self.tabWidget.currentIndex()
+
+        model = self.TABLES[cur_idx]['view'].model()
+        qstr = None
+        if cur_idx == StatsDialog.TAB_MAIN:
+            self._cfg.setSettings(Config.STATS_FILTER_TEXT, text)
+            self._set_events_query()
+            return
+        elif cur_idx == StatsDialog.TAB_NODES:
+            qstr = self._get_nodes_filter_query(model.query().lastQuery(), text)
+        elif cur_idx == StatsDialog.TAB_RULES and self.fwTable.isVisible():
+            self.TABLES[self.TAB_FIREWALL]['view'].filterByQuery(text)
+            return
+        elif self.IN_DETAIL_VIEW[cur_idx] == True:
+            qstr = self._get_indetail_filter_query(model.query().lastQuery(), text)
+        else:
+            where_clause = self._get_filter_line_clause(cur_idx, text)
+            qstr = self._db.get_query( self.TABLES[cur_idx]['name'], self.TABLES[cur_idx]['display_fields'] ) + \
+                where_clause + self._get_order()
+            if text == "":
+                qstr = qstr + self._get_limit()
+
+        if qstr != None:
+            self.setQuery(model, qstr)
+
+    def _cb_limit_combo_changed(self, idx):
+        if self.tabWidget.currentIndex() == self.TAB_MAIN:
+            self._set_events_query()
+        else:
+            model = self._get_active_table().model()
+            qstr = model.query().lastQuery()
+            if "LIMIT" in qstr:
+                qs = qstr.split(" LIMIT ")
+                q = qs[0]
+                l = qs[1]
+                qstr = q + self._get_limit()
+            else:
+                qstr = qstr + self._get_limit()
+            self.setQuery(model, qstr)
+
+    def _cb_combo_action_changed(self, idx):
+        if self.tabWidget.currentIndex() != self.TAB_MAIN:
+            return
+
+        self._cfg.setSettings(Config.STATS_GENERAL_FILTER_ACTION, idx)
+        self._set_events_query()
+
+    def _cb_clean_sql_clicked(self, idx):
+        cur_idx = self.tabWidget.currentIndex()
+        if self.tabWidget.currentIndex() == StatsDialog.TAB_RULES:
+            self._db.empty_rule(self.TABLES[cur_idx]['label'].text())
+        elif self.IN_DETAIL_VIEW[cur_idx]:
+            self._del_by_field(cur_idx, self.TABLES[cur_idx]['name'], self.TABLES[cur_idx]['label'].text())
+        else:
+            self._db.clean(self.TABLES[cur_idx]['name'])
+        self._refresh_active_table()
+
+    def _cb_cmd_back_clicked(self, idx):
+        try:
+            cur_idx = self.tabWidget.currentIndex()
+            self.IN_DETAIL_VIEW[cur_idx] = False
+
+            self._set_active_widgets(cur_idx, False)
+            if cur_idx == StatsDialog.TAB_RULES:
+                self._restore_rules_tab_widgets(True)
+                return
+            elif cur_idx == StatsDialog.TAB_PROCS:
+                self.cmdProcDetails.setVisible(False)
+
+            model = self._get_active_table().model()
+            where_clause = ""
+            if self.TABLES[cur_idx]['filterLine'] != None:
+                filter_text = self.TABLES[cur_idx]['filterLine'].text()
+                where_clause = self._get_filter_line_clause(cur_idx, filter_text)
+
+            self.setQuery(model,
+                        self._db.get_query(
+                            self.TABLES[cur_idx]['name'],
+                            self.TABLES[cur_idx]['display_fields']) + where_clause + " " + self._get_order() + self._get_limit()
+                        )
+        finally:
+            self._restore_details_view_columns(
+                self.TABLES[cur_idx]['view'].horizontalHeader(),
+                "{0}{1}".format(Config.STATS_VIEW_COL_STATE, cur_idx)
+            )
+            self._restore_scroll_value()
+            self._restore_last_selected_row()
+
+    def _cb_main_table_double_clicked(self, row):
+        prev_idx = self.tabWidget.currentIndex()
+        data = row.data()
+        idx = row.column()
+        cur_idx = 1
+
+        if idx == StatsDialog.COL_NODE:
+            cur_idx = self.TAB_NODES
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_NODE).data()
+            self.tabWidget.setCurrentIndex(cur_idx)
+            self._set_active_widgets(prev_idx, True, str(data))
+            self._set_nodes_query(data)
+
+        elif idx == StatsDialog.COL_RULES:
+            cur_idx = self.TAB_RULES
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_RULES).data()
+            r_name, node = self._set_rules_tab_active(row, cur_idx, self.COL_RULES, self.COL_NODE)
+            self._set_active_widgets(prev_idx, True, str(data))
+            self._set_rules_query(r_name, node)
+
+        elif idx == StatsDialog.COL_DSTIP:
+            cur_idx = self.TAB_ADDRS
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            rowdata = row.model().index(row.row(), self.COL_DSTIP).data()
+            ip = rowdata
+            self.LAST_SELECTED_ITEM = ip
+            self.tabWidget.setCurrentIndex(cur_idx)
+            self._set_active_widgets(prev_idx, True, ip)
+            self._set_addrs_query(ip)
+
+        elif idx == StatsDialog.COL_DSTHOST:
+            cur_idx = self.TAB_HOSTS
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            rowdata = row.model().index(row.row(), self.COL_DSTHOST).data()
+            host = rowdata
+            self.LAST_SELECTED_ITEM = host
+            self.tabWidget.setCurrentIndex(cur_idx)
+            self._set_active_widgets(prev_idx, True, host)
+            self._set_hosts_query(host)
+
+        elif idx == StatsDialog.COL_DSTPORT:
+            cur_idx = self.TAB_PORTS
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            rowdata = row.model().index(row.row(), self.COL_DSTPORT).data()
+            port = rowdata
+            self.LAST_SELECTED_ITEM = port
+            self.tabWidget.setCurrentIndex(cur_idx)
+            self._set_active_widgets(prev_idx, True, port)
+            self._set_ports_query(port)
+
+        elif idx == StatsDialog.COL_UID:
+            cur_idx = self.TAB_USERS
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            rowdata = row.model().index(row.row(), self.COL_UID).data()
+            uid = rowdata
+            self.LAST_SELECTED_ITEM = uid
+            self.tabWidget.setCurrentIndex(cur_idx)
+            self._set_active_widgets(prev_idx, True, uid)
+            self._set_users_query(uid)
+
+        else:
+            cur_idx = self.TAB_PROCS
+            self.IN_DETAIL_VIEW[cur_idx] = True
+            self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_PROCS).data()
+            self.tabWidget.setCurrentIndex(cur_idx)
+            self._set_active_widgets(prev_idx, True, self.LAST_SELECTED_ITEM)
+            self._set_process_query(self.LAST_SELECTED_ITEM)
+
+        self._restore_details_view_columns(
+            self.TABLES[cur_idx]['view'].horizontalHeader(),
+            "{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx)
+        )
+
+    def _cb_table_double_clicked(self, row):
+        cur_idx = self.tabWidget.currentIndex()
+        if self.IN_DETAIL_VIEW[cur_idx]:
+            return
+
+        if cur_idx == self.TAB_RULES and self.fwTable.isVisible():
+            uuid = row.model().index(row.row(), 1).data(QtCore.Qt.UserRole+1)
+            addr = row.model().index(row.row(), 2).data(QtCore.Qt.UserRole+1)
+            self._fw_dialog.load_rule(addr, uuid)
+            return
+
+
+        self.IN_DETAIL_VIEW[cur_idx] = True
+        self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_TIME).data()
+        self.LAST_SCROLL_VALUE = self.TABLES[cur_idx]['view'].vScrollBar.value()
+
+        data = row.data()
+
+        if cur_idx == self.TAB_RULES:
+            rule_name = row.model().index(row.row(), self.COL_R_NAME).data()
+            self._set_active_widgets(cur_idx, True, rule_name)
+            r_name, node = self._set_rules_tab_active(row, cur_idx, self.COL_R_NAME, self.COL_R_NODE)
+            self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_R_NAME).data()
+            self._set_rules_query(r_name, node)
+            self._restore_details_view_columns(
+                self.TABLES[cur_idx]['view'].horizontalHeader(),
+                "{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx)
+            )
+            return
+        if cur_idx == self.TAB_NODES:
+            data = row.model().index(row.row(), self.COL_NODE).data()
+            self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_NODE).data()
+        if cur_idx > self.TAB_RULES:
+            self.LAST_SELECTED_ITEM = row.model().index(row.row(), self.COL_WHAT).data()
+            data = row.model().index(row.row(), self.COL_WHAT).data()
+
+
+        self._set_active_widgets(cur_idx, True, str(data))
+
+        if cur_idx == StatsDialog.TAB_NODES:
+            self._set_nodes_query(data)
+        elif cur_idx == StatsDialog.TAB_HOSTS:
+            self._set_hosts_query(data)
+        elif cur_idx == StatsDialog.TAB_PROCS:
+            self._set_process_query(data)
+        elif cur_idx == StatsDialog.TAB_ADDRS:
+            lbl_text = self.TABLES[cur_idx]['label'].text()
+            if lbl_text != "":
+                asn = self.asndb.get_asn(lbl_text)
+                if asn != "":
+                    lbl_text += " (" + asn + ")"
+            self.TABLES[cur_idx]['label'].setText(lbl_text)
+            self._set_addrs_query(data)
+        elif cur_idx == StatsDialog.TAB_PORTS:
+            self._set_ports_query(data)
+        elif cur_idx == StatsDialog.TAB_USERS:
+            self._set_users_query(data)
+
+        self._restore_details_view_columns(
+            self.TABLES[cur_idx]['view'].horizontalHeader(),
+            "{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx)
+        )
+
+    def _cb_prefs_clicked(self):
+        self._prefs_dialog.show()
+
+    def _cb_rules_filter_combo_changed(self, idx):
+        if idx == self.RULES_TREE_APPS:
+            self._set_rules_filter()
+        elif idx == self.RULES_COMBO_PERMANENT:
+            self._set_rules_filter(self.RULES_TREE_APPS, self.RULES_TREE_PERMANENT)
+        elif idx == self.RULES_COMBO_TEMPORARY:
+            self._set_rules_filter(self.RULES_TREE_APPS, self.RULES_TREE_TEMPORARY)
+        elif idx == self.RULES_COMBO_FW:
+            self._set_rules_filter(-1, self.RULES_TREE_FIREWALL)
+
+    def _cb_rules_tree_item_expanded(self, item):
+        self.rulesTreePanel.resizeColumnToContents(0)
+        self.rulesTreePanel.resizeColumnToContents(1)
+
+    def _cb_rules_tree_item_double_clicked(self, item, col):
+        # TODO: open fw chain editor
+        pass
+
+    def _cb_rules_tree_item_clicked(self, item, col):
+        """
+        Event fired when the user clicks on the left panel of the rules tab
+        """
+        item_model = self.rulesTreePanel.indexFromItem(item, col)
+        item_row = item_model.row()
+        parent = item.parent()
+        parent_row = -1
+        node_addr = ""
+        fw_table = ""
+
+        rulesHeader = self.rulesTable.horizontalHeader()
+        self._cfg.setSettings(Config.STATS_RULES_COL_STATE, rulesHeader.saveState())
+
+        self._clear_rows_selection()
+
+        # FIXME: find a clever way of handling these options
+
+        # top level items
+        if parent != None:
+            parent_model = self.rulesTreePanel.indexFromItem(parent, 0)
+            parent_row = parent_model.row()
+            node_addr = parent_model.data()
+
+            # 1st level items: nodes, rules types
+            if parent.parent() != None:
+                parent = parent.parent()
+                parent_model = self.rulesTreePanel.indexFromItem(parent, 0)
+                item_row =  self.FILTER_TREE_FW_TABLE
+                parent_row = self.RULES_TREE_FIREWALL
+                fw_table = parent_model.data()
+
+                # 2nd level items: chains
+                if parent.parent() != None:
+                    parent = parent.parent()
+                    parent_model = self.rulesTreePanel.indexFromItem(parent.parent(), 0)
+                    item_row =  self.FILTER_TREE_FW_CHAIN
+                    parent_row = self.RULES_TREE_FIREWALL
+
+        if node_addr == None:
+            return
+
+        showFwTable = (parent_row == self.RULES_TREE_FIREWALL or (parent_row == -1 and item_row == self.RULES_TREE_FIREWALL))
+        self.fwTable.setVisible(showFwTable)
+        self.rulesTable.setVisible(not showFwTable)
+        self.rulesScrollBar.setVisible(self.rulesTable.isVisible())
+
+        self._set_rules_filter(parent_row, item_row, item.text(0), node_addr, fw_table)
+
+    def _cb_rules_splitter_moved(self, pos, index):
+        self.comboRulesFilter.setVisible(pos == 0)
+        self._cfg.setSettings(Config.STATS_RULES_SPLITTER_POS, self.rulesSplitter.saveState())
+
+    def _cb_start_clicked(self):
+        if self.daemon_connected == False:
+            self.startButton.setChecked(False)
+            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():
+            nid, noti = self._nodes.start_interception(_callback=self._notification_callback)
+        else:
+            nid, noti = self._nodes.stop_interception(_callback=self._notification_callback)
+
+        self._notifications_sent[nid] = noti
+
+    def _cb_node_start_clicked(self):
+        addr = self.TABLES[self.TAB_NODES]['label'].text()
+        if addr == "":
+            return
+        if self.nodeStartButton.isChecked():
+            self._update_nodes_interception_status()
+            nid, noti = self._nodes.start_interception(_addr=addr, _callback=self._notification_callback)
+        else:
+            self._update_nodes_interception_status(disable=True)
+            nid, noti = self._nodes.stop_interception(_addr=addr, _callback=self._notification_callback)
+
+        self._notifications_sent[nid] = noti
+
+    def _cb_node_prefs_clicked(self):
+        addr = self.TABLES[self.TAB_NODES]['label'].text()
+        if addr == "":
+            return
+        self._prefs_dialog.show_node_prefs(addr)
+
+    def _cb_node_delete_clicked(self):
+        ret = Message.yes_no(
+            QC.translate("stats", "    You are about to delete this node.    "),
+            QC.translate("stats", "    Are you sure?"),
+            QtWidgets.QMessageBox.Warning)
+        if ret == QtWidgets.QMessageBox.Cancel:
+            return
+
+        addr = self.TABLES[self.TAB_NODES]['label'].text()
+        if self._db.remove("DELETE FROM nodes WHERE addr = '{0}'".format(addr)) == False:
+            Message.ok(
+                QC.translate("stats",
+                                "<b>Error deleting node</b><br><br>",
+                                "{0}").format(addr),
+                QtWidgets.QMessageBox.Warning)
+            return
+
+        self._nodes.delete(addr)
+        self.TABLES[self.TAB_NODES]['cmd'].click()
+        self.TABLES[self.TAB_NODES]['label'].setText("")
+        self._refresh_active_table()
+
+    def _cb_new_rule_clicked(self):
+        self._rules_dialog.new_rule()
+
+    def _cb_edit_rule_clicked(self):
+        cur_idx = self.tabWidget.currentIndex()
+        records = self._get_rule(self.TABLES[cur_idx]['label'].text(), self.nodeRuleLabel.text())
+        if records == None:
+            return
+
+        self._rules_dialog.edit_rule(records, self.nodeRuleLabel.text())
+
+    def _cb_del_rule_clicked(self):
+        ret = Message.yes_no(
+            QC.translate("stats", "    You are about to delete this rule.    "),
+            QC.translate("stats", "    Are you sure?"),
+            QtWidgets.QMessageBox.Warning)
+        if ret == QtWidgets.QMessageBox.Cancel:
+            return
+
+        self._del_rule(self.TABLES[self.tabWidget.currentIndex()]['label'].text(), self.nodeRuleLabel.text())
+        self.TABLES[self.TAB_RULES]['cmd'].click()
+        self.nodeRuleLabel.setText("")
+        self._refresh_active_table()
+
+    def _cb_enable_rule_toggled(self, state):
+        rule = ui_pb2.Rule(name=self.TABLES[self.tabWidget.currentIndex()]['label'].text())
+        rule.enabled = False
+        rule.action = ""
+        rule.duration = ""
+        rule.operator.type = ""
+        rule.operator.operand = ""
+        rule.operator.data = ""
+
+        notType = ui_pb2.DISABLE_RULE
+        if state == True:
+            notType = ui_pb2.ENABLE_RULE
+        rule.enabled = state
+        noti = ui_pb2.Notification(type=notType, rules=[rule])
+        self._notification_trigger.emit(noti)
+
+    def _cb_prev_button_clicked(self):
+        model = self._get_active_table().model()
+        model.fetchMore()
+
+    def _cb_next_button_clicked(self):
+        model = self._get_active_table().model()
+        model.fetchMore()
+
+    def _cb_help_button_clicked(self):
+        QuickHelp.show(
+            QC.translate("stats",
+                         "<p><b>Quick help</b></p>" \
+                         "<p>- Use CTRL+c to copy selected rows.</p>" \
+                         "<p>- Use Home,End,PgUp,PgDown,PgUp,Up or Down keys to navigate rows.</p>" \
+                         "<p>- Use right click on a row to stop refreshing the view.</p>" \
+                         "<p>- Selecting more than one row also stops refreshing the view.</p>"
+                         "<p>- On the Events view, clicking on columns Node, Process or Rule<br>" \
+                         "jumps to the view of the selected item.</p>" \
+                         "<p>- On the rest of the views, double click on a row to get detailed<br>" \
+                         " information.</p><br>" \
+                         "<p>For more information visit the <a href=\"{0}\">wiki</a></p>" \
+                         "<br>".format(Config.HELP_URL)
+                         )
+        )
+
+    # must be called after setModel() or setQuery()
+    def _show_columns(self):
+        cols = self._cfg.getSettings(Config.STATS_SHOW_COLUMNS)
+        if cols == None:
+            return
+
+        for c in range(StatsDialog.GENERAL_COL_NUM):
+            self.eventsTable.setColumnHidden(c, str(c) not in cols)
+
+    def _update_status_label(self, running=False, text=FIREWALL_DISABLED):
+        self.statusLabel.setText("%12s" % text)
+        if running:
+            self.statusLabel.setStyleSheet('color: green; margin: 5px')
+            self.startButton.setIcon(self.iconPause)
+        else:
+            self.statusLabel.setStyleSheet('color: rgb(206, 92, 0); margin: 5px')
+            self.startButton.setIcon(self.iconStart)
+
+        self._add_rulesTree_nodes()
+        self._add_rulesTree_fw_chains()
+
+    def _get_rulesTree_item(self, index):
+        try:
+            return self.rulesTreePanel.topLevelItem(index)
+        except Exception:
+            return None
+
+    def _add_rulesTree_nodes(self):
+        if self._nodes.count() > 0:
+            nodesItem = self.rulesTreePanel.topLevelItem(self.RULES_TREE_NODES)
+            nodesItem.takeChildren()
+            for n in self._nodes.get_nodes():
+                nodesItem.addChild(QtWidgets.QTreeWidgetItem([n]))
+
+    def _find_tree_fw_items(self, item_data):
+        """find fw items by data stored in UserRole role.
+        """
+        fwItem = self.rulesTreePanel.topLevelItem(self.RULES_TREE_FIREWALL)
+        it = QtWidgets.QTreeWidgetItemIterator(fwItem)
+        items = []
+        while it.value():
+            x = it.value()
+            if x.data(0, QtCore.Qt.UserRole) == item_data:
+                items.append(x)
+            it+=1
+
+        return items
+
+    def _add_rulesTree_fw_chains(self):
+        expanded = list()
+        selected = None
+        scrollValue = self.rulesTreePanel.verticalScrollBar().value()
+        fwItem = self.rulesTreePanel.topLevelItem(self.RULES_TREE_FIREWALL)
+        it = QtWidgets.QTreeWidgetItemIterator(fwItem)
+        # save tree selected rows
+        try:
+            while it.value():
+                x = it.value()
+                if x.isExpanded():
+                    expanded.append(x)
+                if x.isSelected():
+                    selected = x
+                it += 1
+        except Exception:
+            pass
+
+        self.rulesTreePanel.setAnimated(False)
+        fwItem.takeChildren()
+        self.rulesTreePanel.setItemWidget(fwItem, 1, self.fwTreeEdit)
+        chains = self._fw.get_chains()
+        for addr in chains:
+            # add nodes
+            nodeRoot = QtWidgets.QTreeWidgetItem(["{0}".format(addr)])
+            nodeRoot.setData(0, QtCore.Qt.UserRole, addr)
+            fwItem.addChild(nodeRoot)
+            for nodeChains in chains[addr]:
+                # exclude legacy system rules
+                if len(nodeChains) == 0:
+                    continue
+                for cc in nodeChains:
+                    # add tables
+                    tableName = "{0}-{1}".format(cc.Table, cc.Family)
+                    nodeTable = QtWidgets.QTreeWidgetItem([tableName])
+                    nodeTable.setData(0, QtCore.Qt.UserRole, "{0}-{1}".format(addr, tableName))
+
+                    chainName = "{0}-{1}".format(cc.Name, cc.Hook)
+                    nodeChain = QtWidgets.QTreeWidgetItem([chainName, cc.Policy])
+                    nodeChain.setData(0, QtCore.Qt.UserRole, "{0}-{1}".format(addr, chainName))
+
+                    items = self._find_tree_fw_items("{0}-{1}".format(addr, tableName))
+                    if len(items) == 0:
+                        # add table
+                        nodeTable.addChild(nodeChain)
+                        nodeRoot.addChild(nodeTable)
+                    else:
+                        # add chains
+                        node = items[0]
+                        node.addChild(nodeChain)
+
+        # restore previous selected rows
+        try:
+            for item in expanded:
+                items = self.rulesTreePanel.findItems(item.text(0), QtCore.Qt.MatchRecursive)
+                for it in items:
+                        it.setExpanded(True)
+                        if selected != None and selected.text(0) == it.text(0):
+                            it.setSelected(True)
+        except:
+            pass
+
+        self.rulesTreePanel.verticalScrollBar().setValue(scrollValue)
+        self.rulesTreePanel.setAnimated(True)
+        self.rulesTreePanel.resizeColumnToContents(0)
+        self.rulesTreePanel.resizeColumnToContents(1)
+        expanded = None
+
+    def _clear_rows_selection(self):
+        cur_idx = self.tabWidget.currentIndex()
+        self.TABLES[cur_idx]['view'].clearSelection()
+
+    def _are_rows_selected(self):
+        cur_idx = self.tabWidget.currentIndex()
+        view = self.TABLES[cur_idx]['view']
+        ret = False
+        if view != None:
+            ret = len(view.selectionModel().selectedRows(0)) > 0
+        return ret
+
+    def _get_rule(self, rule_name, node_name):
+        """
+        get rule records, given the name of the rule and the node
+        """
+        cur_idx = self.tabWidget.currentIndex()
+        records = self._db.get_rule(rule_name, node_name)
+        if records.next() == False:
+            print("[stats dialog] edit rule, no records: ", rule_name, node_name)
+            if self.TABLES[cur_idx]['cmd'] != None:
+                self.TABLES[cur_idx]['cmd'].click()
+            return None
+
+        return records
+
+    def _get_filter_line_clause(self, idx, text):
+        if text == "":
+            return ""
+
+        if idx == StatsDialog.TAB_RULES:
+            return " WHERE rules.name LIKE '%{0}%' OR rules.operator_data LIKE '%{1}%' ".format(text, text)
+        elif idx == StatsDialog.TAB_HOSTS or \
+            idx == StatsDialog.TAB_PROCS or \
+            idx == StatsDialog.TAB_ADDRS or \
+            idx == StatsDialog.TAB_PORTS or \
+            idx == StatsDialog.TAB_USERS:
+            return " WHERE what LIKE '%{0}%' ".format(text)
+
+        return ""
+
+    def _get_limit(self):
+        return " " + self.LIMITS[self.limitCombo.currentIndex()]
+
+    def _get_order(self, field=None):
+        cur_idx = self.tabWidget.currentIndex()
+        order_field = self.TABLES[cur_idx]['last_order_by']
+        if field != None:
+           order_field  = field
+        return " ORDER BY %s %s" % (order_field, self.SORT_ORDER[self.TABLES[cur_idx]['last_order_to']])
+
+    def _refresh_active_table(self):
+        cur_idx = self.tabWidget.currentIndex()
+        model = self._get_active_table().model()
+        lastQuery = model.query().lastQuery()
+        if "LIMIT" not in lastQuery:
+            lastQuery += self._get_limit()
+        self.setQuery(model, lastQuery)
+        self.TABLES[cur_idx]['view'].refresh()
+
+    def _get_active_table(self):
+        if self.tabWidget.currentIndex() == self.TAB_RULES and self.fwTable.isVisible():
+            return self.TABLES[self.TAB_FIREWALL]['view']
+        return self.TABLES[self.tabWidget.currentIndex()]['view']
+
+    def _set_active_widgets(self, prev_idx, state, label_txt=""):
+        cur_idx = self.tabWidget.currentIndex()
+        self._clear_rows_selection()
+        self.TABLES[cur_idx]['label'].setVisible(state)
+        self.TABLES[cur_idx]['label'].setText(label_txt)
+        self.TABLES[cur_idx]['cmd'].setVisible(state)
+
+        if self.TABLES[cur_idx]['filterLine'] != None:
+            self.TABLES[cur_idx]['filterLine'].setVisible(not state)
+
+        if self.TABLES[cur_idx].get('cmdCleanStats') != None:
+            if cur_idx == StatsDialog.TAB_RULES or cur_idx == StatsDialog.TAB_NODES:
+                self.TABLES[cur_idx]['cmdCleanStats'].setVisible(state)
+
+        if cur_idx == StatsDialog.TAB_NODES:
+            self._update_nodes_interception_status(state)
+            self.nodeDeleteButton.setVisible(state)
+            self.nodeActionsButton.setVisible(state)
+
+        header = self.TABLES[cur_idx]['view'].horizontalHeader()
+        if state == True:
+            # going to details state
+            self._cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_COL_STATE, prev_idx), header.saveState())
+        else:
+            # going to normal state
+            self._cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx), header.saveState())
+
+    def _restore_last_selected_row(self):
+        cur_idx = self.tabWidget.currentIndex()
+        col = self.COL_TIME
+        if cur_idx == self.TAB_RULES:
+            col = self.TAB_RULES
+        elif cur_idx == self.TAB_NODES:
+            col = self.TAB_RULES
+
+        #self.TABLES[cur_idx]['view'].selectItem(self.LAST_SELECTED_ITEM, col)
+        self.LAST_SELECTED_ITEM = ""
+
+    def _restore_scroll_value(self):
+        if self.LAST_SCROLL_VALUE != None:
+            cur_idx = self.tabWidget.currentIndex()
+            self.TABLES[cur_idx]['view'].vScrollBar.setValue(self.LAST_SCROLL_VALUE)
+            self.LAST_SCROLL_VALUE = None
+
+    def _restore_details_view_columns(self, header, settings_key):
+        header.blockSignals(True);
+        # In order to resize the last column of a view, we firstly force a
+        # resizeToContens call.
+        # Secondly set resizeMode to Interactive (allow to move columns by
+        # users + programmatically)
+        header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents)
+        header.setSectionResizeMode(QtWidgets.QHeaderView.Interactive)
+
+        col_state = self._cfg.getSettings(settings_key)
+        if type(col_state) == QtCore.QByteArray:
+            header.restoreState(col_state)
+
+        header.blockSignals(False);
+
+    def _restore_rules_tab_widgets(self, active):
+        self.delRuleButton.setVisible(not active)
+        self.editRuleButton.setVisible(not active)
+        self.nodeRuleLabel.setText("")
+        self.rulesTreePanel.setVisible(active)
+
+        if not active:
+            return
+
+        self.rulesSplitter.refresh()
+        self.comboRulesFilter.setVisible(self.rulesTreePanel.width() == 0)
+
+        items = self.rulesTreePanel.selectedItems()
+        if len(items) == 0:
+            self._set_rules_filter()
+            return
+
+        rindex = item_m = self.rulesTreePanel.indexFromItem(items[0], 0)
+        parent = item_m.parent()
+
+        # find current root item of the tree panel
+        while rindex.parent().isValid():
+            rindex = rindex.parent()
+        rnum = rindex.row()
+
+        if parent != None and rnum != self.RULES_TREE_FIREWALL:
+            self._set_rules_filter(parent.row(), item_m.row(), item_m.data())
+        else:
+            # when going back to the rules view, reset selection and select the
+            # Apps view.
+            index = self.rulesTreePanel.model().index(self.RULES_TREE_APPS, 0)
+            self.rulesTreePanel.setCurrentIndex(index)
+            self._set_rules_filter()
+
+
+    def _set_rules_tab_active(self, row, cur_idx, name_idx, node_idx):
+        self._restore_rules_tab_widgets(False)
+        self.comboRulesFilter.setVisible(False)
+
+        r_name = row.model().index(row.row(), name_idx).data()
+        node = row.model().index(row.row(), node_idx).data()
+        self.nodeRuleLabel.setText(node)
+
+        self.fwTable.setVisible(False)
+        self.rulesTable.setVisible(True)
+        self.tabWidget.setCurrentIndex(cur_idx)
+
+        return r_name, node
+
+    def _set_events_query(self):
+        if self.tabWidget.currentIndex() != self.TAB_MAIN:
+            return
+
+        model = self.TABLES[self.TAB_MAIN]['view'].model()
+        qstr = self._db.get_query(self.TABLES[self.TAB_MAIN]['name'], self.TABLES[self.TAB_MAIN]['display_fields'])
+
+        filter_text = self.filterLine.text()
+        action = ""
+        if self.comboAction.currentIndex() == 1:
+            action = "action = \"{0}\"".format(Config.ACTION_ALLOW)
+        elif self.comboAction.currentIndex() == 2:
+            action = "action = \"{0}\"".format(Config.ACTION_DENY)
+        elif self.comboAction.currentIndex() == 3:
+            action = "action = \"{0}\"".format(Config.ACTION_REJECT)
+
+        # FIXME: use prepared statements
+        if filter_text == "":
+            if action != "":
+                qstr += " WHERE " + action
+        else:
+            if action != "":
+                action += " AND "
+            qstr += " WHERE " + action + " ("\
+                    " process LIKE '%" + filter_text + "%'" \
+                    " OR process_args LIKE '%" + filter_text + "%'" \
+                    " OR src_port LIKE '%" + filter_text + "%'" \
+                    " OR src_ip LIKE '%" + filter_text + "%'" \
+                    " OR dst_ip LIKE '%" + filter_text + "%'" \
+                    " OR dst_host LIKE '%" + filter_text + "%'" \
+                    " OR dst_port LIKE '%" + filter_text + "%'" \
+                    " OR rule LIKE '%" + filter_text + "%'" \
+                    " OR node LIKE '%" + filter_text + "%'" \
+                    " OR time LIKE '%" + filter_text + "%'" \
+                    " OR pid LIKE '%" + filter_text + "%'" \
+                    " OR uid LIKE '%" + filter_text + "%'" \
+                    " OR protocol LIKE '%" + filter_text + "%')" \
+
+        qstr += self._get_order() + self._get_limit()
+        self.setQuery(model, qstr)
+
+    def _set_nodes_query(self, data):
+
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.action as {1}, " \
+                "count(c.process) as {2}, " \
+                "c.uid as {3}, " \
+                "c.protocol as {4}, " \
+                "c.src_port as {5}, " \
+                "c.src_ip as {6}, " \
+                "c.dst_ip as {7}, " \
+                "c.dst_host as {8}, " \
+                "c.dst_port as {9}, " \
+                "c.pid as {10}, " \
+                "c.process as {11}, " \
+                "c.process_args as {12}, " \
+                "c.process_cwd as CWD, " \
+                "c.rule as {13} " \
+            "FROM connections as c " \
+            "WHERE c.node = '{14}' GROUP BY {15}, c.process_args, c.uid, c.src_ip, c.dst_ip, c.dst_host, c.dst_port, c.protocol {16}".format(
+                self.COL_STR_TIME,
+                self.COL_STR_ACTION,
+                self.COL_STR_HITS,
+                self.COL_STR_UID,
+                self.COL_STR_PROTOCOL,
+                self.COL_STR_SRC_PORT,
+                self.COL_STR_SRC_IP,
+                self.COL_STR_DST_IP,
+                self.COL_STR_DST_HOST,
+                self.COL_STR_DST_PORT,
+                self.COL_STR_PID,
+                self.COL_STR_PROCESS,
+                self.COL_STR_PROC_CMDLINE,
+                self.COL_STR_RULE,
+                data,
+                self.COL_STR_PROCESS,
+                self._get_order() + self._get_limit()))
+
+    def _get_nodes_filter_query(self, lastQuery, text):
+        base_query = lastQuery.split("GROUP BY")
+        if not self.IN_DETAIL_VIEW[self.TAB_NODES]:
+            base_query = lastQuery.split("ORDER BY")
+
+        qstr = base_query[0]
+        if "AND" in qstr:
+            # strip out ANDs if any
+            os = qstr.split('AND')
+            qstr = os[0]
+
+        if text != "":
+            if self.IN_DETAIL_VIEW[self.TAB_NODES]:
+                qstr += "AND (c.time LIKE '%{0}%' OR " \
+                    "c.action LIKE '%{0}%' OR " \
+                    "c.uid LIKE '%{0}%' OR " \
+                    "c.pid LIKE '%{0}%' OR " \
+                    "c.src_port LIKE '%{0}%' OR " \
+                    "c.dst_port LIKE '%{0}%' OR " \
+                    "c.src_ip LIKE '%{0}%' OR " \
+                    "c.dst_ip LIKE '%{0}%' OR " \
+                    "c.dst_host LIKE '%{0}%' OR " \
+                    "c.process LIKE '%{0}%' OR " \
+                    "c.process_cwd LIKE '%{0}%' OR " \
+                    "c.process_args LIKE '%{0}%')".format(text)
+            else:
+                if "WHERE" in qstr:
+                    w = qstr.split('WHERE')
+                    qstr = w[0]
+
+                qstr += "WHERE (" \
+                    "last_connection LIKE '%{0}%' OR " \
+                    "addr LIKE '%{0}%' OR " \
+                    "status LIKE '%{0}%' OR " \
+                    "hostname LIKE '%{0}%' OR " \
+                    "version LIKE '%{0}%'" \
+                    ")".format(text)
+
+        if self.IN_DETAIL_VIEW[self.TAB_NODES]:
+            qstr += " GROUP BY" + base_query[1]
+        else:
+            qstr += " ORDER BY" + base_query[1]
+
+        return qstr
+
+    def _update_nodes_interception_status(self, show=True, disable=False):
+        addr = self.TABLES[self.TAB_NODES]['label'].text()
+        node_cfg = self._nodes.get_node(addr)
+        if node_cfg == None:
+            self.nodeStartButton.setVisible(False)
+            self.nodePrefsButton.setVisible(False)
+            self.nodeDeleteButton.setVisible(False)
+            self.nodeActionsButton.setVisible(False)
+            return
+        self.nodeStartButton.setVisible(show)
+        self.nodePrefsButton.setVisible(show)
+        self.nodeActionsButton.setVisible(show)
+        if not node_cfg['data'].isFirewallRunning or disable:
+            self.nodeStartButton.setChecked(False)
+            self.nodeStartButton.setDown(False)
+            self.nodeStartButton.setIcon(self.iconStart)
+        else:
+            self.nodeStartButton.setIcon(self.iconPause)
+            self.nodeStartButton.setChecked(True)
+            self.nodeStartButton.setDown(True)
+
+    def _set_rules_filter(self, parent_row=-1, item_row=0, what="", what1="", what2=""):
+        section = self.FILTER_TREE_APPS
+
+        if parent_row == -1:
+            self.fwTable.setVisible(item_row == self.RULES_TREE_FIREWALL)
+            self.rulesTable.setVisible(item_row != self.RULES_TREE_FIREWALL)
+            if item_row == self.RULES_TREE_NODES:
+                section=self.FILTER_TREE_NODES
+                what=""
+            elif item_row == self.RULES_TREE_FIREWALL:
+                self.TABLES[self.TAB_FIREWALL]['view'].model().filterAll()
+                return
+            else:
+                section=self.FILTER_TREE_APPS
+                what=""
+
+        elif parent_row == self.RULES_TREE_APPS:
+            if item_row == self.RULES_TREE_PERMANENT:
+                section=self.FILTER_TREE_APPS
+                what=self.RULES_TYPE_PERMANENT
+            elif item_row == self.RULES_TREE_TEMPORARY:
+                section=self.FILTER_TREE_APPS
+                what=self.RULES_TYPE_TEMPORARY
+
+        elif parent_row == self.RULES_TREE_NODES:
+            section=self.FILTER_TREE_NODES
+
+        elif parent_row == self.RULES_TREE_FIREWALL:
+            if item_row == self.FILTER_TREE_FW_NODE:
+                self.TABLES[self.TAB_FIREWALL]['view'].filterByNode(what)
+            elif item_row == self.FILTER_TREE_FW_TABLE:
+                parm = what.split("-")
+                if len(parm) < 2:
+                    return
+                self.TABLES[self.TAB_FIREWALL]['view'].filterByTable(what1, parm[0], parm[1])
+            elif item_row == self.FILTER_TREE_FW_CHAIN: # + table
+                parm = what.split("-")
+                tbl = what1.split("-")
+                self.TABLES[self.TAB_FIREWALL]['view'].filterByChain(what2, tbl[0], tbl[1], parm[0], parm[1])
+            return
+
+        if section == self.FILTER_TREE_APPS:
+            if what == self.RULES_TYPE_TEMPORARY:
+                what = "WHERE r.duration != '%s'" % Config.DURATION_ALWAYS
+            elif what == self.RULES_TYPE_PERMANENT:
+                what = "WHERE r.duration = '%s'" % Config.DURATION_ALWAYS
+        elif section == self.FILTER_TREE_NODES and what != "":
+            what = "WHERE r.node = '%s'" % what
+
+        filter_text = self.filterLine.text()
+        if filter_text != "":
+            if what == "":
+                what = "WHERE"
+            else:
+                what = what + " AND"
+            what = what + " r.name LIKE '%{0}%'".format(filter_text)
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT {0} FROM rules as r {1} {2} {3}".format(
+            self.TABLES[self.TAB_RULES]['display_fields'],
+            what,
+            self._get_order(),
+            self._get_limit()
+        ))
+
+    def _set_rules_query(self, rule_name="", node=""):
+        if node != "":
+            node = "c.node = '%s'" % node
+        if rule_name != "":
+            rule_name = "c.rule = '%s'" % rule_name
+
+        condition = "%s AND %s" % (rule_name, node) if rule_name != "" and node != "" else ""
+
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.node as {1}, " \
+                "count(c.process) as {2}, " \
+                "c.uid as {3}, " \
+                "c.protocol as {4}, " \
+                "c.src_port as {5}, " \
+                "c.src_ip as {6}, " \
+                "c.dst_ip as {7}, " \
+                "c.dst_host as {8}, " \
+                "c.dst_port as {9}, " \
+                "c.pid as {10}, " \
+                "c.process as {11}, " \
+                "c.process_args as {12}, " \
+                "c.process_cwd as CWD " \
+            "FROM connections as c " \
+            "WHERE {13} GROUP BY c.process, c.process_args, c.uid, c.dst_ip, c.dst_host, c.dst_port {14}".format(
+                self.COL_STR_TIME,
+                self.COL_STR_NODE,
+                self.COL_STR_HITS,
+                self.COL_STR_UID,
+                self.COL_STR_PROTOCOL,
+                self.COL_STR_SRC_PORT,
+                self.COL_STR_SRC_IP,
+                self.COL_STR_DST_IP,
+                self.COL_STR_DST_HOST,
+                self.COL_STR_DST_PORT,
+                self.COL_STR_PID,
+                self.COL_STR_PROCESS,
+                self.COL_STR_PROC_CMDLINE,
+                condition,
+                self._get_order() + self._get_limit()
+            ))
+
+    def _set_hosts_query(self, data):
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.node as {1}, " \
+                "count(c.process) as {2}, " \
+                "c.action as {3}, " \
+                "c.uid as {4}, " \
+                "c.protocol as {5}, " \
+                "c.src_port as {6}, " \
+                "c.src_ip as {7}, " \
+                "c.dst_ip as {9}, " \
+                "c.dst_port as {8}, " \
+                "c.pid as {10}, " \
+                "c.process as {11}, " \
+                "c.process_args as {12}, " \
+                "c.process_cwd as CWD, " \
+                "c.rule as {13} " \
+            "FROM connections as c " \
+            "WHERE c.dst_host = '{14}' GROUP BY c.pid, {15}, c.process_args, c.src_ip, c.dst_ip, c.dst_port, c.protocol, c.action, c.node {16}".format(
+                          self.COL_STR_TIME,
+                          self.COL_STR_NODE,
+                          self.COL_STR_HITS,
+                          self.COL_STR_ACTION,
+                          self.COL_STR_UID,
+                          self.COL_STR_PROTOCOL,
+                          self.COL_STR_SRC_PORT,
+                          self.COL_STR_SRC_IP,
+                          self.COL_STR_DST_IP,
+                          self.COL_STR_DST_PORT,
+                          self.COL_STR_PID,
+                          self.COL_STR_PROCESS,
+                          self.COL_STR_PROC_CMDLINE,
+                          self.COL_STR_RULE,
+                          data,
+                          self.COL_STR_PROCESS,
+                self._get_order("1") + self._get_limit()))
+
+    def _set_process_query(self, data):
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.node as {1}, " \
+                "count(c.dst_ip) as {2}, " \
+                "c.action as {3}, " \
+                "c.uid as {4}, " \
+                "c.protocol as {5}, " \
+                "c.src_port as {6}, " \
+                "c.src_ip as {7}, " \
+                "c.dst_ip as {8}, " \
+                "c.dst_host as {9}, " \
+                "c.dst_port as {10}, " \
+                "c.pid as PID, " \
+                "c.process_args as {11}, " \
+                "c.process_cwd as CWD, " \
+                "c.rule as {12} " \
+            "FROM connections as c " \
+            "WHERE c.process = '{13}' " \
+                      "GROUP BY c.src_ip, c.dst_ip, c.dst_host, c.dst_port, c.uid, c.action, c.node, c.pid, c.process_args {14}".format(
+                          self.COL_STR_TIME,
+                          self.COL_STR_NODE,
+                          self.COL_STR_HITS,
+                          self.COL_STR_ACTION,
+                          self.COL_STR_UID,
+                          self.COL_STR_PROTOCOL,
+                          self.COL_STR_SRC_PORT,
+                          self.COL_STR_SRC_IP,
+                          self.COL_STR_DST_IP,
+                          self.COL_STR_DST_HOST,
+                          self.COL_STR_DST_PORT,
+                          self.COL_STR_PROC_CMDLINE,
+                          self.COL_STR_RULE,
+                          data,
+                          self._get_order("1") + self._get_limit()))
+
+        nrows = self._get_active_table().model().rowCount()
+        self.cmdProcDetails.setVisible(nrows != 0)
+
+    def _set_addrs_query(self, data):
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.node as {1}, " \
+                "count(c.dst_ip) as {2}, " \
+                "c.action as {3}, " \
+                "c.uid as {4}, " \
+                "c.protocol as {5}, " \
+                "c.src_port as {6}, " \
+                "c.src_ip as {7}, " \
+                "c.dst_host as {8}, " \
+                "c.dst_port as {9}, " \
+                "c.pid as {10}, " \
+                "c.process as {11}, " \
+                "c.process_args as {12}, " \
+                "c.process_cwd as CWD, " \
+                "c.rule as {13} " \
+            "FROM connections as c " \
+            "WHERE c.dst_ip = '{14}' GROUP BY c.pid, {15}, c.process_args, c.src_ip, c.dst_port, {16}, c.protocol, c.action, c.uid, c.node {17}".format(
+                          self.COL_STR_TIME,
+                          self.COL_STR_NODE,
+                          self.COL_STR_HITS,
+                          self.COL_STR_ACTION,
+                          self.COL_STR_UID,
+                          self.COL_STR_PROTOCOL,
+                          self.COL_STR_SRC_PORT,
+                          self.COL_STR_SRC_IP,
+                          self.COL_STR_DST_HOST,
+                          self.COL_STR_DST_PORT,
+                          self.COL_STR_PID,
+                          self.COL_STR_PROCESS,
+                          self.COL_STR_PROC_CMDLINE,
+                          self.COL_STR_RULE,
+                          data,
+                          self.COL_STR_PROCESS,
+                          self.COL_STR_DST_HOST,
+                          self._get_order("1") + self._get_limit()))
+
+    def _set_ports_query(self, data):
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.node as {1}, " \
+                "count(c.dst_ip) as {2}, " \
+                "c.action as {3}, " \
+                "c.uid as {4}, " \
+                "c.protocol as {5}, " \
+                "c.src_port as {6}, " \
+                "c.src_ip as {7}, " \
+                "c.dst_ip as {8}, " \
+                "c.dst_host as {9}, " \
+                "c.pid as {10}, " \
+                "c.process as {11}, " \
+                "c.process_args as {12}, " \
+                "c.process_cwd as CWD, " \
+                "c.rule as {13} " \
+            "FROM connections as c " \
+            "WHERE c.dst_port = '{14}' GROUP BY c.pid, {15}, c.process_args, {16}, c.src_ip, c.dst_ip, c.protocol, c.action, c.uid, c.node {17}".format(
+                          self.COL_STR_TIME,
+                          self.COL_STR_NODE,
+                          self.COL_STR_HITS,
+                          self.COL_STR_ACTION,
+                          self.COL_STR_UID,
+                          self.COL_STR_PROTOCOL,
+                          self.COL_STR_SRC_PORT,
+                          self.COL_STR_SRC_IP,
+                          self.COL_STR_DST_IP,
+                          self.COL_STR_DST_HOST,
+                          self.COL_STR_PID,
+                          self.COL_STR_PROCESS,
+                          self.COL_STR_PROC_CMDLINE,
+                          self.COL_STR_RULE,
+                          data,
+                          self.COL_STR_PROCESS,
+                          self.COL_STR_DST_HOST,
+                          self._get_order("1") + self._get_limit()))
+
+    def _set_users_query(self, data):
+        uid = data.split(" ")
+        if len(uid) == 2:
+            uid = uid[1].strip("()")
+        else:
+            uid = uid[0]
+        model = self._get_active_table().model()
+        self.setQuery(model, "SELECT " \
+                "MAX(c.time) as {0}, " \
+                "c.node as {1}, " \
+                "count(c.dst_ip) as {2}, " \
+                "c.action as {3}, " \
+                "c.protocol as {4}, " \
+                "c.src_port as {5}, " \
+                "c.src_ip as {6}, " \
+                "c.dst_ip as {7}, " \
+                "c.dst_host as {8}, " \
+                "c.dst_port as {9}, " \
+                "c.pid as {10}, " \
+                "c.process as {11}, " \
+                "c.process_args as {12}, " \
+                "c.process_cwd as CWD, " \
+                "c.rule as {13} " \
+            "FROM connections as c " \
+            "WHERE c.uid = '{14}' GROUP BY c.pid, {15}, c.process_args, c.src_ip, c.dst_ip, c.dst_host, c.dst_port, c.protocol, c.action, c.node {16}".format(
+                          self.COL_STR_TIME,
+                          self.COL_STR_NODE,
+                          self.COL_STR_HITS,
+                          self.COL_STR_ACTION,
+                          self.COL_STR_PROTOCOL,
+                          self.COL_STR_SRC_PORT,
+                          self.COL_STR_SRC_IP,
+                          self.COL_STR_DST_IP,
+                          self.COL_STR_DST_HOST,
+                          self.COL_STR_DST_PORT,
+                          self.COL_STR_PID,
+                          self.COL_STR_PROCESS,
+                          self.COL_STR_PROC_CMDLINE,
+                          self.COL_STR_RULE,
+                          uid,
+                          self.COL_STR_PROCESS,
+                          self._get_order("1") + self._get_limit()))
+
+    # get the query filtering by text when a tab is in the detail view.
+    def _get_indetail_filter_query(self, lastQuery, text):
+        try:
+            cur_idx = self.tabWidget.currentIndex()
+            base_query = lastQuery.split("GROUP BY")
+            qstr = base_query[0]
+            where = qstr.split("WHERE")[1]  # get SELECT ... WHERE (*)
+            ands = where.split("AND (")[0] # get WHERE (*) AND (...)
+            qstr = qstr.split("WHERE")[0]  # get * WHERE ...
+            qstr += "WHERE %s" % ands
+
+            # if there's no text to filter, strip the filter "AND ()", and
+            # return the original query.
+            if text == "":
+                return
+
+            qstr += "AND (c.time LIKE '%{0}%' OR " \
+                "c.action LIKE '%{0}%' OR " \
+                "c.pid LIKE '%{0}%' OR " \
+                "c.protocol LIKE '%{0}%' OR " \
+                "c.src_port LIKE '%{0}%' OR " \
+                "c.src_ip LIKE '%{0}%' OR ".format(text)
+
+            # exclude from query the field of the view we're filtering by
+            if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_PORTS:
+                qstr += "c.dst_port LIKE '%{0}%' OR ".format(text)
+            if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_ADDRS:
+                qstr += "c.dst_ip LIKE '%{0}%' OR ".format(text)
+            if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_HOSTS:
+                qstr += "c.dst_host LIKE '%{0}%' OR ".format(text)
+            if self.IN_DETAIL_VIEW[cur_idx] != self.TAB_PROCS:
+                qstr += "c.process LIKE '%{0}%' OR ".format(text)
+
+            qstr += "c.process_args LIKE '%{0}%')".format(text)
+
+        finally:
+            if len(base_query) > 1:
+                qstr += " GROUP BY" + base_query[1]
+            return qstr
+
+    @QtCore.pyqtSlot()
+    def _on_settings_saved(self):
+        self._ui_refresh_interval = self._cfg.getInt(Config.STATS_REFRESH_INTERVAL, 0)
+        self._show_columns()
+        self.settings_saved.emit()
+
+    def _on_menu_node_export_clicked(self, triggered):
+        outdir = QtWidgets.QFileDialog.getExistingDirectory(self,
+                                                            os.path.expanduser("~"),
+                                                            QC.translate("stats", 'Select a directory to export rules'),
+                                                            QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks)
+        if outdir == "":
+            return
+
+        node = self.nodesLabel.text()
+        if self._nodes.export_rules(node, outdir) == False:
+            Message.ok("Rules export error",
+                       QC.translate("stats",
+                                    "Error exporting rules"
+                                    ),
+                       QtWidgets.QMessageBox.Warning)
+        else:
+            Message.ok("Rules export",
+                       QC.translate("stats", "Rules exported to {0}".format(outdir)),
+                       QtWidgets.QMessageBox.Information)
+
+
+    def _on_menu_node_import_clicked(self, triggered):
+        rulesdir = QtWidgets.QFileDialog.getExistingDirectory(self,
+                                                                os.path.expanduser("~"),
+                                                                QC.translate("stats", 'Select a directory with rules to import (JSON files)'),
+                                                                QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks)
+        if rulesdir == '':
+                return
+
+        node = self.nodesLabel.text()
+        nid, notif, rules = self._nodes.import_rules(addr=node, rulesdir=rulesdir, callback=self._notification_callback)
+        if nid != None:
+                self._notifications_sent[nid] = notif
+                # TODO: add rules per node and after receiving the notification
+                for node in self._nodes.get_nodes():
+                    self._nodes.add_rules(node, rules)
+
+                Message.ok("Rules import",
+                        QC.translate("stats", "Rules imported fine"),
+                        QtWidgets.QMessageBox.Information)
+                if self.tabWidget.currentIndex() == self.TAB_RULES:
+                    self._refresh_active_table()
+        else:
+                Message.ok("Rules import error",
+                        QC.translate("stats",
+                                        "Error importing rules from {0}".format(rulesdir)
+                                        ),
+                        QtWidgets.QMessageBox.Warning)
+
+
+
+    def _on_menu_exit_clicked(self, triggered):
+        self.close_trigger.emit()
+
+    def _on_menu_export_clicked(self, triggered):
+        outdir = QtWidgets.QFileDialog.getExistingDirectory(self,
+                                                            os.path.expanduser("~"),
+                                                            QC.translate("stats", 'Select a directory to export rules'),
+                                                            QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks)
+        if outdir == "":
+            return
+
+        errors = []
+        for node in self._nodes.get_nodes():
+            if self._nodes.export_rules(node, outdir) == False:
+                errors.append(node)
+           # apply_to_node()...
+
+        if len(errors) > 0:
+            errorlist = ""
+            for e in errors:
+                errorlist = errorlist + e + "<br>"
+            Message.ok("Rules export error",
+                       QC.translate("stats",
+                                    "Error exporting rules of the following nodes:<br><br>{0}"
+                                    .format(errorlist)
+                                    ),
+                       QtWidgets.QMessageBox.Warning)
+        else:
+            Message.ok("Rules export",
+                       QC.translate("stats", "Rules exported to {0}".format(outdir)),
+                       QtWidgets.QMessageBox.Information)
+
+    def _on_menu_import_clicked(self, triggered):
+       rulesdir = QtWidgets.QFileDialog.getExistingDirectory(self,
+                                                            os.path.expanduser("~"),
+                                                            QC.translate("stats", 'Select a directory with rules to import (JSON files)'),
+                                                            QtWidgets.QFileDialog.ShowDirsOnly | QtWidgets.QFileDialog.DontResolveSymlinks)
+       if rulesdir == '':
+            return
+
+       nid, notif, rules = self._nodes.import_rules(rulesdir=rulesdir, callback=self._notification_callback)
+       if nid != None:
+            self._notifications_sent[nid] = notif
+            # TODO: add rules per node and after receiving the notification
+            for node in self._nodes.get_nodes():
+                self._nodes.add_rules(node, rules)
+
+            Message.ok("Rules import",
+                       QC.translate("stats", "Rules imported fine"),
+                       QtWidgets.QMessageBox.Information)
+            if self.tabWidget.currentIndex() == self.TAB_RULES:
+                self._refresh_active_table()
+       else:
+            Message.ok("Rules import error",
+                       QC.translate("stats",
+                                    "Error importing rules from {0}".format(rulesdir)
+                                    ),
+                       QtWidgets.QMessageBox.Warning)
+
+    def _on_menu_export_csv_clicked(self, triggered):
+        tab_idx = self.tabWidget.currentIndex()
+
+        filename = QtWidgets.QFileDialog.getSaveFileName(self,
+                    QC.translate("stats", 'Save as CSV'),
+                    self._file_names[tab_idx],
+                    'All Files (*);;CSV Files (*.csv)')[0].strip()
+        if filename == '':
+            return
+
+        with self._lock:
+            table = self._tables[tab_idx]
+            ncols = table.model().columnCount()
+            nrows = table.model().rowCount()
+            cols = []
+
+            for col in range(0, ncols):
+                cols.append(table.model().headerData(col, QtCore.Qt.Horizontal))
+
+            with open(filename, 'w') as csvfile:
+                w = csv.writer(csvfile, dialect='excel')
+                w.writerow(cols)
+
+                if tab_idx == self.TAB_MAIN:
+                    w.writerows(table.model().dumpRows())
+                else:
+                    for row in range(0, nrows):
+                        values = []
+                        for col in range(0, ncols):
+                            values.append(table.model().index(row, col).data())
+                        w.writerow(values)
+
+    def _setup_table(self, widget, tableWidget, table_name, fields="*", group_by="", order_by="2", sort_direction=SORT_ORDER[1], limit="", resize_cols=(), model=None, delegate=None, verticalScrollBar=None, tracking_column=COL_TIME):
+        tableWidget.setSortingEnabled(True)
+        if model == None:
+            model = self._db.get_new_qsql_model()
+        if verticalScrollBar != None:
+            tableWidget.setVerticalScrollBar(verticalScrollBar)
+        tableWidget.verticalScrollBar().sliderPressed.connect(self._cb_scrollbar_pressed)
+        tableWidget.verticalScrollBar().sliderReleased.connect(self._cb_scrollbar_released)
+        tableWidget.setTrackingColumn(tracking_column)
+
+        self.setQuery(model, "SELECT " + fields + " FROM " + table_name + group_by + " ORDER BY " + order_by + " " + sort_direction + limit)
+        tableWidget.setModel(model)
+
+        if delegate != None:
+            action = self._actions.get(delegate)
+            if action != None:
+                tableWidget.setItemDelegate(ColorizedDelegate(tableWidget, actions=action))
+
+        header = tableWidget.horizontalHeader()
+        if header != None:
+            header.sortIndicatorChanged.connect(self._cb_table_header_clicked)
+
+            for _, col in enumerate(resize_cols):
+                header.setSectionResizeMode(col, QtWidgets.QHeaderView.ResizeToContents)
+
+        cur_idx = self.tabWidget.currentIndex()
+        self._cfg.setSettings("{0}{1}".format(Config.STATS_VIEW_DETAILS_COL_STATE, cur_idx), header.saveState())
+        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)
+
+    def _needs_refresh(self):
+        diff = datetime.datetime.now() - self._last_update
+        if diff.seconds < self._ui_refresh_interval:
+            return False
+
+        return True
+
+    # launched from a thread
+    def update(self, is_local=True, stats=None, need_query_update=True):
+        # lock mandatory when there're multiple clients
+        with self._lock:
+            if stats is not None:
+                self._stats = stats
+            # do not update any tab if the window is not visible
+            if self.isVisible() and self.isMinimized() == False and self._needs_refresh():
+                self._trigger.emit(is_local, need_query_update)
+                self._last_update = datetime.datetime.now()
+
+    def update_status(self):
+        self.startButton.setDown(self.daemon_connected)
+        self.startButton.setChecked(self.daemon_connected)
+        self.startButton.setDisabled(not self.daemon_connected)
+        if self.daemon_connected:
+            self._update_status_label(running=True, text=self.FIREWALL_RUNNING)
+        else:
+            self._update_status_label(running=False, text=self.FIREWALL_STOPPED)
+            self.statusLabel.setStyleSheet('color: red; margin: 5px')
+
+    @QtCore.pyqtSlot(bool, bool)
+    def _on_update_triggered(self, is_local, need_query_update=False):
+        if self._stats is None:
+            self.daemonVerLabel.setText("")
+            self.uptimeLabel.setText("")
+            self.rulesLabel.setText("")
+            self.consLabel.setText("")
+            self.droppedLabel.setText("")
+        else:
+            nodes = self._nodes.count()
+            self.daemonVerLabel.setText(self._stats.daemon_version)
+            if nodes <= 1:
+                self.uptimeLabel.setText(str(datetime.timedelta(seconds=self._stats.uptime)))
+                self.rulesLabel.setText("%s" % self._stats.rules)
+                self.consLabel.setText("%s" % self._stats.connections)
+                self.droppedLabel.setText("%s" % self._stats.dropped)
+            else:
+                self.uptimeLabel.setText("")
+                self.rulesLabel.setText("")
+                self.consLabel.setText("")
+                self.droppedLabel.setText("")
+
+            if need_query_update and not self._are_rows_selected():
+                self._refresh_active_table()
+
+    # prevent a click on the window's x
+    # from quitting the whole application
+    def closeEvent(self, e):
+        self._save_settings()
+        e.accept()
+        self.hide()
+
+    def hideEvent(self, e):
+        self._save_settings()
+
+    # https://gis.stackexchange.com/questions/86398/how-to-disable-the-escape-key-for-a-dialog
+    def keyPressEvent(self, event):
+        if not event.key() == QtCore.Qt.Key_Escape:
+            super(StatsDialog, self).keyPressEvent(event)
+
+    def setQuery(self, model, q):
+        if self._context_menu_active == True or self.scrollbar_active == True:
+            return
+        with self._lock:
+            try:
+                model.query().clear()
+                model.setQuery(q, self._db_sqlite)
+                if model.lastError().isValid():
+                    print("setQuery() error: ", model.lastError().text())
+
+                if self.tabWidget.currentIndex() != self.TAB_MAIN:
+                    self.labelRowsCount.setText("{0}".format(model.totalRowCount))
+                else:
+                    self.labelRowsCount.setText("")
+            except Exception as e:
+                print(self._address, "setQuery() exception: ", e)
diff --git a/ui/opensnitch/firewall/__init__.py b/ui/opensnitch/firewall/__init__.py
new file mode 100644 (file)
index 0000000..ee3dac4
--- /dev/null
@@ -0,0 +1,233 @@
+from PyQt5.QtCore import QObject, QCoreApplication as QC
+from google.protobuf import json_format
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from opensnitch.nodes import Nodes
+from .enums import *
+from .rules import *
+from .chains import *
+from .utils import Utils
+from .exprs import *
+from .profiles import *
+
+class Firewall(QObject):
+    __instance = None
+
+    @staticmethod
+    def instance():
+        if Firewall.__instance == None:
+            Firewall.__instance = Firewall()
+        return Firewall.__instance
+
+    def __init__(self, parent=None):
+        QObject.__init__(self)
+        self._nodes = Nodes.instance()
+        self.rules = Rules(self._nodes)
+        self.chains = Chains(self._nodes)
+
+    def switch_rules(self, key, old_pos, new_pos):
+        pass
+
+    def add_rule(self, addr, rule):
+        return self.rules.add(addr, rule)
+
+    def insert_rule(self, addr, rule, position=0):
+        return self.rules.insert(addr, rule, position)
+
+    def update_rule(self, addr, uuid, rule):
+        return self.rules.update(addr, uuid, rule)
+
+    def delete_rule(self, addr, uuid):
+        return self.rules.delete(addr, uuid)
+
+    def change_rule_field(self, addr, uuid, field, value):
+        addr, chain = self.get_rule_by_uuid(uuid)
+        if chain is None:
+            return None, None
+
+        if field == Rules.FIELD_ENABLED:
+            chain.Rules[0].Enabled = value
+        elif field == Rules.FIELD_TARGET:
+            chain.Rules[0].Target = value
+        return self.update_rule(addr, uuid, chain)
+
+    def enable_rule(self, addr, uuid, enable):
+        addr, chain = self.get_rule_by_uuid(uuid)
+        if chain is None:
+            return None, None
+
+        chain.Rules[0].Enabled = enable
+        return self.update_rule(addr, uuid, chain)
+
+    def filter_rules(self, nail):
+        """
+        """
+        chains = []
+        for addr in self._nodes.get_nodes():
+            node = self._nodes.get_node(addr)
+            if not 'firewall' in node:
+                return chains
+            for n in node['firewall'].SystemRules:
+                for c in n.Chains:
+                    for r in c.Rules:
+                        add_rule = False
+                        if nail == r.UUID:
+                            add_rule = True
+
+                        if nail in c.Family or \
+                                nail in c.Hook or \
+                                nail in r.Description or \
+                                nail in r.Target or \
+                                nail in r.TargetParameters:
+                            add_rule = True
+                        else:
+                            for e in r.Expressions:
+                                if add_rule:
+                                    break
+                                expr_vals = "".join("{0} {1}".format(h.Key, h.Value) for h in e.Statement.Values)
+                                #print(nail in expr_vals, r.Description)
+                                if nail in e.Statement.Op or \
+                                        nail in e.Statement.Name or \
+                                        nail in e.Statement.Values or \
+                                        nail in expr_vals:
+                                    add_rule = True
+
+                        if add_rule:
+                            chains.append(Rules.to_array(addr, c, r))
+
+        return chains
+
+    def filter_by_table(self, addr, table, family):
+        """get rules by table"""
+        chains = []
+        node = self._nodes.get_node(addr)
+        if not 'firewall' in node:
+            return chains
+        for n in node['firewall'].SystemRules:
+            for c in n.Chains:
+                for r in c.Rules:
+                    if c.Table == table and c.Family == family:
+                        chains.append(Rules.to_array(addr, c, r))
+
+        return chains
+
+    def filter_by_chain(self, addr, table, family, chain, hook):
+        """get rules by chain"""
+        chains = []
+        node = self._nodes.get_node(addr)
+        if not 'firewall' in node:
+            return chains
+        for n in node['firewall'].SystemRules:
+            for c in n.Chains:
+                for r in c.Rules:
+                    if c.Table == table and c.Family == family and c.Name == chain and c.Hook == hook:
+                        chains.append(Rules.to_array(addr, c, r))
+
+        return chains
+
+    def swap_rules(self, view, addr, uuid, old_pos, new_pos):
+        return self.rules.swap(view, addr, uuid, old_pos, new_pos)
+
+    def get_rule_by_uuid(self, uuid):
+        """get rule by uuid, in string format
+        """
+        if uuid == "":
+            return None, None
+        for addr in self._nodes.get_nodes():
+            node = self._nodes.get_node(addr)
+            if not 'fwrules' in node:
+                continue
+            r = node['fwrules'].get(uuid)
+            if r != None:
+                return addr, r
+
+        return None, None
+
+    def get_protorule_by_uuid(self, addr, uuid):
+        """get protobuffer rule by uuid.
+        """
+        return self.rules.get_by_uuid(addr, uuid)
+
+    def get_node_rules(self, addr):
+        return self.rules.get_by_node(addr)
+
+    def get_chains(self):
+        return self.chains.get()
+
+    def get_rules(self):
+        return self.rules.get()
+
+    def rule_to_json(self, rule):
+        return Rules.to_json(rule)
+
+    def apply_profile(self, node_addr, json_profile):
+        """
+        Apply a profile to the firewall configuration.
+
+        Given a chain (table+family+type+hook), apply its policy, and any rules
+        defined.
+        """
+        try:
+            holder = ui_pb2.FwChain()
+            profile = json_format.Parse(json_profile, holder)
+
+            fwcfg = self._nodes.get_node(node_addr)['firewall']
+            for sdx, n in enumerate(fwcfg.SystemRules):
+                for cdx, c in enumerate(n.Chains):
+
+                    if c.Hook.lower() == profile.Hook and \
+                            c.Type.lower() == profile.Type and \
+                            c.Family.lower() == profile.Family and \
+                            c.Table.lower() == profile.Table:
+
+                        fwcfg.SystemRules[sdx].Chains[cdx].Policy = profile.Policy
+                        for r in profile.Rules:
+                            temp_c = ui_pb2.FwChain()
+                            temp_c.CopyFrom(c)
+                            del temp_c.Rules[:]
+                            temp_c.Rules.extend([r])
+
+                            if self.rules.is_duplicated(node_addr, temp_c):
+                                continue
+                            self.add_rule(node_addr, temp_c)
+
+                        self.rules.rulesUpdated.emit()
+                        return True, ""
+        except Exception as e:
+            return False, "{0}".format(e)
+
+        return False, QC.translate("firewall", "profile not applied")
+
+    def delete_profile(self, node_addr, json_profile):
+        try:
+            holder = ui_pb2.FwChain()
+            profile = json_format.Parse(json_profile, holder)
+
+            fwcfg = self._nodes.get_node(node_addr)['firewall']
+            for sdx, n in enumerate(fwcfg.SystemRules):
+                for cdx, c in enumerate(n.Chains):
+                    if c.Hook.lower() == profile.Hook and \
+                            c.Type.lower() == profile.Type and \
+                            c.Family.lower() == profile.Family and \
+                            c.Table.lower() == profile.Table:
+
+                        if profile.Policy == ProfileDropInput.value:
+                            profile.Policy = ProfileAcceptInput.value
+
+                        del_candidates = []
+                        for rdx, r in enumerate(c.Rules):
+                            for pr in profile.Rules:
+                                if r.UUID == pr.UUID:
+                                    # we cannot delete the rule here, otherwise
+                                    # we'd modify the items of the loop.
+                                    del_candidates.append(rdx)
+                        if len(del_candidates) > 0:
+                            for rdx in del_candidates:
+                                if rdx == len(c.Rules): # last rule
+                                    rdx = rdx - 1
+                                self.delete_rule(node_addr, c.Rules[rdx].UUID)
+
+        except Exception as e:
+            return False, "{0}".format(e)
+        return False, QC.translate("firewall", "profile not deleted")
diff --git a/ui/opensnitch/firewall/chains.py b/ui/opensnitch/firewall/chains.py
new file mode 100644 (file)
index 0000000..d7606c3
--- /dev/null
@@ -0,0 +1,258 @@
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from .enums import *
+
+class Chains():
+
+    def __init__(self, nodes):
+        self._nodes = nodes
+
+    def get(self):
+        chains = {}
+        for node in self._nodes.get_nodes():
+            chains[node] = self.get_node_chains(node)
+        return chains
+
+    def get_node_chains(self, addr):
+        node = self._nodes.get_node(addr)
+        if node == None:
+            return rules
+        if not 'firewall' in node:
+            return rules
+
+        chains = []
+        for c in node['firewall'].SystemRules:
+            # Chains node does not exist on <= v1.5.x
+            try:
+                chains.append(c.Chains)
+            except Exception:
+                pass
+        return chains
+
+    def get_node_chains(self, addr):
+        node = self._nodes.get_node(addr)
+        if node == None:
+            return rules
+        if not 'firewall' in node:
+            return rules
+
+        chains = []
+        for c in node['firewall'].SystemRules:
+            # Chains node does not exist on <= v1.5.x
+            try:
+                chains.append(c.Chains)
+            except Exception:
+                pass
+        return chains
+
+
+
+    def get_policy(self, node_addr=None, hook=Hooks.INPUT.value, _type=ChainType.FILTER.value, family=Family.INET.value):
+        fwcfg = self._nodes.get_node(node_addr)['firewall']
+        for sdx, n in enumerate(fwcfg.SystemRules):
+            for cdx, c in enumerate(n.Chains):
+                if c.Hook.lower() == hook and c.Type.lower() == _type and c.Family.lower() == family:
+                    return c.Policy
+
+        return None
+
+    def set_policy(self, node_addr, hook=Hooks.INPUT.value, _type=ChainType.FILTER.value, family=Family.INET.value, policy=Policy.DROP):
+        fwcfg = self._nodes.get_node(node_addr)['firewall']
+        for sdx, n in enumerate(fwcfg.SystemRules):
+            for cdx, c in enumerate(n.Chains):
+                # XXX: support only "inet" family (ipv4/ipv6)? or allow to
+                # specify ipv4 OR/AND ipv6? some systems have ipv6 disabled
+                if c.Hook.lower() == hook and c.Type.lower() == _type and c.Family.lower() == family:
+                    fwcfg.SystemRules[sdx].Chains[cdx].Policy = policy
+
+                    if wantedHook == Fw.Hooks.INPUT.value and wantedPolicy == Fw.Policy.DROP.value:
+                        fwcfg.SystemRules[sdx].Chains[cdx].Rules.extend([rule.Rules[0]])
+                        self._nodes.add_fw_config(node_addr, fwcfg)
+                    return True
+        return False
+
+
+    @staticmethod
+    def new(
+        name="",
+        table=Table.FILTER.value,
+        family=Family.INET.value,
+        ctype="",
+        hook=Hooks.INPUT.value
+    ):
+        chain = ui_pb2.FwChain()
+        chain.Name = name
+        chain.Table = table
+        chain.Family = family
+        chain.Type = ctype
+        chain.Hook = hook
+
+        return chain
+
+# man nft
+# Table 6. Standard priority names, family and hook compatibility matrix
+# Name     │ Value │ Families                   │ Hooks
+# raw      │ -300  │ ip, ip6, inet              │ all
+# mangle   │ -150  │ ip, ip6, inet              │ all
+# dstnat   │ -100  │ ip, ip6, inet              │ prerouting
+# filter   │ 0     │ ip, ip6, inet, arp, netdev │ all
+# security │ 50    │ ip, ip6, inet              │ all
+# srcnat   │ 100   │ ip, ip6, inet              │ postrouting
+#
+
+class ChainFilter(Chains):
+    """
+    ChainFilter returns a new chain of type filter.
+
+    The name of the chain is the one listed with: nft list table inet filter.
+    It corresponds with the hook name, but can be a random name.
+    """
+
+    @staticmethod
+    def input(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.INPUT.value
+        chain.Table = Table.FILTER.value
+        chain.Family = family
+        chain.Type = ChainType.FILTER.value
+        chain.Hook = Hooks.INPUT.value
+
+        return chain
+
+    @staticmethod
+    def output(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.OUTPUT.value
+        chain.Table = Table.FILTER.value
+        chain.Family = family
+        chain.Type = ChainType.FILTER.value
+        chain.Hook = Hooks.OUTPUT.value
+
+        return chain
+
+    @staticmethod
+    def forward(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.FORWARD.value
+        chain.Table = Table.FILTER.value
+        chain.Family = family
+        chain.Type = ChainType.FILTER.value
+        chain.Hook = Hooks.FORWARD.value
+
+        return chain
+
+
+
+class ChainMangle(Chains):
+    """
+    ChainMangle returns a new chain of type mangle.
+
+    The name of the chain is the one listed with: nft list table inet mangle.
+    It corresponds with the hook name, but can be a random name.
+    """
+
+    @staticmethod
+    def output(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.OUTPUT.value
+        chain.Table = Table.MANGLE.value
+
+        chain.Family = family
+        chain.Type = ChainType.MANGLE.value
+        chain.Hook = Hooks.OUTPUT.value
+
+        return chain
+
+    @staticmethod
+    def input(family=Family.INET.value):
+        chain = ui_pb2.FwChain(family=Family.INET.value)
+        chain.Name = Hooks.INPUT.value
+        chain.Table = Table.MANGLE.value
+
+        chain.Family = family
+        chain.Type = ChainType.MANGLE.value
+        chain.Hook = Hooks.INPUT.value
+
+        return chain
+
+    @staticmethod
+    def forward(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.FORWARD.value
+        chain.Table = Table.MANGLE.value
+
+        chain.Family = family
+        chain.Type = ChainType.MANGLE.value
+        chain.Hook = Hooks.FORWARD.value
+
+        return chain
+
+
+    @staticmethod
+    def prerouting(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.PREROUTING.value
+        chain.Table = Table.MANGLE.value
+
+        chain.Family = family
+        chain.Type = ChainType.MANGLE.value
+        chain.Hook = Hooks.PREROUTING.value
+
+        return chain
+
+    @staticmethod
+    def postrouting(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.POSTROUTING.value
+        chain.Table = Table.MANGLE.value
+
+        chain.Family = family
+        chain.Type = ChainType.MANGLE.value
+        chain.Hook = Hooks.POSTROUTING.value
+
+        return chain
+
+class ChainDstNAT(Chains):
+    """
+    ChainDstNAT returns a new chain of type dstnat.
+
+    The name of the chain is the one listed with: nft list table inet nat.
+    It corresponds with the hook name, but can be a random name.
+    """
+
+    @staticmethod
+    def prerouting(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.PREROUTING.value
+        chain.Table = Table.NAT.value
+
+        chain.Family = family
+        chain.Type = ChainType.DNAT.value
+        chain.Hook = Hooks.PREROUTING.value
+
+        return chain
+
+    @staticmethod
+    def output(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.OUTPUT.value
+        chain.Table = Table.NAT.value
+
+        chain.Family = family
+        chain.Type = ChainType.DNAT.value
+        chain.Hook = Hooks.OUTPUT.value
+
+        return chain
+
+    @staticmethod
+    def postrouting(family=Family.INET.value):
+        chain = ui_pb2.FwChain()
+        chain.Name = Hooks.POSTROUTING.value
+        chain.Table = Table.NAT.value
+
+        chain.Family = family
+        chain.Type = ChainType.SNAT.value
+        chain.Hook = Hooks.POSTROUTING.value
+
+        return chain
diff --git a/ui/opensnitch/firewall/enums.py b/ui/opensnitch/firewall/enums.py
new file mode 100644 (file)
index 0000000..82996df
--- /dev/null
@@ -0,0 +1,127 @@
+from opensnitch.utils import Enums
+from opensnitch.config import Config
+
+class Verdicts(Enums):
+    EMPTY = ""
+    ACCEPT = Config.ACTION_ACCEPT
+    DROP = Config.ACTION_DROP
+    REJECT = Config.ACTION_REJECT
+    RETURN = Config.ACTION_RETURN
+    QUEUE = Config.ACTION_QUEUE
+    DNAT = Config.ACTION_DNAT
+    SNAT = Config.ACTION_SNAT
+    REDIRECT = Config.ACTION_REDIRECT
+    TPROXY = Config.ACTION_TPROXY
+    #MASQUERADE = Config.ACTION_MASQUERADE
+    #LOG = Config.ACTION_LOG
+    STOP = Config.ACTION_STOP
+
+
+
+class Policy(Enums):
+    ACCEPT = "accept"
+    DROP = "drop"
+
+class Table(Enums):
+    FILTER = "filter"
+    MANGLE = "mangle"
+    NAT = "nat"
+
+class Hooks(Enums):
+    INPUT  ="input"
+    OUTPUT  ="output"
+    FORWARD = "forward"
+    PREROUTING = "prerouting"
+    POSTROUTING = "postrouting"
+
+class PortProtocols(Enums):
+    TCPUDP = "tcp,udp"
+    TCP = "tcp"
+    UDP = "udp"
+    UDPLITE = "udplite"
+    SCTP = "sctp"
+    DCCP = "dccp"
+
+class Protocols(Enums):
+    TCP = "tcp"
+    UDP = "udp"
+    UDPLITE = "udplite"
+    SCTP = "sctp"
+    DCCP = "dccp"
+    ICMP = "icmp"
+    ICMPv6 = "icmpv6"
+    AH = "ah"
+    ETHERNET = "ethernet"
+    GREP = "gre"
+    IP = "ip"
+    IPIP = "ipip"
+    L2TP = "l2tp"
+    COMP = "comp"
+    IGMP = "igmp"
+    ESP = "esp"
+    RAW = "raw"
+    ENCAP = "encap"
+
+class Family(Enums):
+    INET = "inet"
+    IPv4 = "ip"
+    IPv6 = "ip6"
+
+class ChainType(Enums):
+    FILTER = "filter"
+    MANGLE = "mangle"
+    ROUTE = "route"
+    SNAT = "natsource"
+    DNAT = "natdest"
+
+class Operator(Enums):
+    EQUAL = "=="
+    NOT_EQUAL = "!="
+    GT_THAN = ">="
+    GT = ">"
+    LT_THAN = "<="
+    LT = "<"
+
+class TimeUnits(Enums):
+    SECOND = "second"
+    MINUTE = "minute"
+    HOUR = "hour"
+    DAY = "day"
+
+class RateUnits(Enums):
+    BYTES = "bytes"
+    KBYTES = "kbytes"
+    MBYTES = "mbytes"
+    GBYTES = "gbytes"
+
+class Statements(Enums):
+    """Enum of known (allowed) statements:
+        [tcp,udp,ip] ...
+    """
+    # we may need in the future:
+    # ANY = tcp,udp,udplite,sctp,dccp
+    TCPUDP = "tcp,udp"
+    TCP = "tcp"
+    UDP = "udp"
+    UDPLITE = "udplite"
+    SCTP = "sctp"
+    DCCP = "dccp"
+    ICMP = "icmp"
+    ICMPv6 = "icmpv6"
+
+    SPORT = "sport"
+    DPORT = "dport"
+    DADDR = "daddr"
+    SADDR = "saddr"
+
+    IP = "ip"
+    IP6 = "ip6"
+    IIFNAME = "iifname"
+    OIFNAME = "oifname"
+    CT = "ct"
+    META = "meta"
+    COUNTER = "counter"
+    NAME = "name"
+    LOG = "log"
+    QUOTA = "quota"
+    LIMIT = "limit"
diff --git a/ui/opensnitch/firewall/exprs.py b/ui/opensnitch/firewall/exprs.py
new file mode 100644 (file)
index 0000000..c192468
--- /dev/null
@@ -0,0 +1,123 @@
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from .enums import *
+
+class Expr():
+    """
+    Expr returns a new nftables expression that defines a match or an action:
+        tcp dport 22, udp sport 53
+        log prefix "xxx"
+
+    Attributes:
+        op (string): operator (==, !=, ...).
+        what (string): name of the statement (tcp, udp, ip, ...)
+        value (tuple): array of values (dport -> 22, etc).
+    """
+    @staticmethod
+    def new(op, what, values):
+        expr = ui_pb2.Expressions()
+        expr.Statement.Op = op
+        expr.Statement.Name = what
+
+        for val in values:
+            exprValues = ui_pb2.StatementValues()
+            exprValues.Key = val[0]
+            exprValues.Value = val[1]
+            expr.Statement.Values.extend([exprValues])
+
+        return expr
+
+class ExprCt(Enums):
+    STATE = "state"
+    NEW = "new"
+    ESTABLISHED = "established"
+    RELATED = "related"
+    INVALID = "invalid"
+    SET = "set"
+    MARK = "mark"
+
+class ExprMeta(Enums):
+    SET = "set"
+    MARK = "mark"
+    L4PROTO = "l4proto"
+    SKUID = "skuid"
+    SKGID = "skgid"
+    PROTOCOL = "protocol"
+    PRIORITY = "priority"
+
+class ExprIface(Enums):
+    IIFNAME = "iifname"
+    OIFNAME = "oifname"
+
+class ExprICMP(Enums):
+    ECHO_REQUEST = "echo-request"
+    ECHO_REPLY = "echo-reply"
+    SOURCE_QUENCH = "source-quench"
+    DEST_UNREACHABLE = "destination-unreachable"
+    ROUTER_ADVERTISEMENT = "router-advertisement"
+    ROUTER_SOLICITATION = "router-solicitation"
+    REDIRECT = "redirect"
+    TIME_EXCEEDED = "time-exceeded"
+    INFO_REQUEST = "info-request"
+    INFO_REPLY = "info-reply"
+    PARAMETER_PROBLEM = "parameter-problem"
+    TIMESTAMP_REQUEST = "timestamp-request"
+    TIMESTAMP_REPLY = "timestamp-reply"
+    ADDRESS_MASK_REQUEST = "address-mask-request"
+    ADDRESS_MASK_REPLY = "address-mask-reply"
+
+    # IPv6
+    PACKET_TOO_BIG = "packet-too-big"
+    NEIGHBOUR_SOLICITATION = "neighbour-solicitation"
+    NEIGHBOUR_ADVERTISEMENT = "neighbour-advertisement"
+
+class ExprICMPRejectCodes(Enums):
+    NO_ROUTE = "no-route"
+    PROT_UNREACHABLE = "prot-unreachable"
+    PORT_UNREACHABLE = "port-unreachable"
+    NET_UNREACHABLE = "net-unreachable"
+    ADDR_UNREACHABLE = "addr-unreachable"
+    HOST_UNREACHABLE = "host-unreachable"
+    NET_PROHIBITED = "net-prohibited"
+    HOST_PROHIBITED = "host-prohibited"
+    ADMIN_PROHIBITED = "admin-prohibited"
+    REJECT_ROUTE = "reject-route"
+    REJECT_POLICY_FAIL = "policy-fail"
+
+class ExprLog(Enums):
+    LOG = "log"
+    LEVEL = "level"
+    PREFIX = "prefix"
+
+class ExprLogLevels(Enums):
+    EMERG = "emerg"
+    ALERT = "alert"
+    CRIT = "crit"
+    ERR = "err"
+    WARN = "warn"
+    NOTICE = "notice"
+    INFO = "info"
+    DEBUG = "debug"
+    AUDIT = "audit"
+
+class ExprCounter(Enums):
+    COUNTER = "counter"
+    PACKETS = "packets"
+    BYTES = "bytes"
+    NAME = "name"
+
+class ExprLimit(Enums):
+    OVER = "over"
+    LIMIT = "limit"
+    UNITS = "units"
+    RATE_UNITS = "rate-units"
+    TIME_UNITS = "time-units"
+
+class ExprQuota(Enums):
+    QUOTA = "quota"
+    OVER = "over"
+    UNTIL = "until"
+    USED = "used"
+    UNIT = "unit"
diff --git a/ui/opensnitch/firewall/profiles.py b/ui/opensnitch/firewall/profiles.py
new file mode 100644 (file)
index 0000000..6d580c3
--- /dev/null
@@ -0,0 +1,157 @@
+
+import glob
+import json
+import os.path
+
+
+class Profiles():
+
+    @staticmethod
+    def load_predefined_profiles():
+        profiles = glob.glob("/etc/opensnitchd/system-fw.d/profiles/*.profile")
+        p = []
+        for pr_path in profiles:
+            with open(pr_path) as f:
+                p.append({os.path.basename(pr_path): json.load(f)})
+
+        return p
+
+
+class ProfileAcceptOutput():
+    value = {
+        "Name": "accept-mangle-output",
+        "Table": "mangle",
+        "Family": "inet",
+        "Priority": "",
+        "Type": "mangle",
+        "Hook": "output",
+        "Policy": "accept",
+        "Rules": [
+        ]
+    }
+
+
+class ProfileDropOutput():
+    value = {
+        "Name": "drop-mangle-output",
+        "Table": "mangle",
+        "Family": "inet",
+        "Priority": "",
+        "Type": "mangle",
+        "Hook": "output",
+        "Policy": "drop",
+        "Rules": [
+        ]
+    }
+
+
+class ProfileAcceptForward():
+    value = {
+        "Name": "accept-mangle-forward",
+        "Table": "mangle",
+        "Family": "inet",
+        "Priority": "",
+        "Type": "mangle",
+        "Hook": "forward",
+        "Policy": "accept",
+        "Rules": [
+        ]
+    }
+
+
+class ProfileDropForward():
+    value = {
+        "Name": "drop-mangle-forward",
+        "Table": "mangle",
+        "Family": "inet",
+        "Priority": "",
+        "Type": "mangle",
+        "Hook": "forward",
+        "Policy": "drop",
+        "Rules": [
+        ]
+    }
+
+
+class ProfileAcceptInput():
+    value = {
+        "Name": "accept-filter-input",
+        "Table": "filter",
+        "Family": "inet",
+        "Priority": "",
+        "Type": "filter",
+        "Hook": "input",
+        "Policy": "accept",
+        "Rules": [
+        ]
+    }
+
+
+class ProfileDropInput():
+    """
+    Set input filter table policy to DROP and add the needed rules to allow
+    outbound connections.
+    """
+
+    # TODO: delete dropInput profile's rules
+    value = {
+        "Name": "drop-filter-input",
+        "Table": "filter",
+        "Family": "inet",
+        "Priority": "",
+        "Type": "filter",
+        "Hook": "input",
+        "Policy": "drop",
+        "Rules": [
+            {
+                "Table": "",
+                "Chain": "",
+                "UUID": "profile-drop-inbound-2d7e6fe4-c21d-11ec-99a6-3c970e298b0c",
+                "Enabled": True,
+                "Position": "0",
+                "Description": "[profile-drop-inbound] allow localhost connections",
+                "Parameters": "",
+                "Expressions": [
+                    {
+                        "Statement": {
+                            "Op": "",
+                            "Name": "iifname",
+                            "Values": [
+                                {
+                                    "Key": "lo",
+                                    "Value": ""
+                                }
+                            ]
+                        }
+                    }
+                ],
+                "Target": "accept",
+                "TargetParameters": ""
+            },
+            {
+                "Enabled": True,
+                "Description": "[profile-drop-inbound] allow established,related connections",
+                "UUID": "profile-drop-inbound-e1fc1a1c-c21c-11ec-9a2a-3c970e298b0c",
+                "Expressions": [
+                    {
+                        "Statement": {
+                            "Op": "",
+                            "Name": "ct",
+                            "Values": [
+                                {
+                                    "Key": "state",
+                                    "Value": "related"
+                                },
+                                {
+                                    "Key": "state",
+                                    "Value": "established"
+                                }
+                            ]
+                        }
+                    }
+                ],
+                "Target": "accept",
+                "TargetParameters": ""
+            }
+        ]
+    }
diff --git a/ui/opensnitch/firewall/rules.py b/ui/opensnitch/firewall/rules.py
new file mode 100644 (file)
index 0000000..0ec35fd
--- /dev/null
@@ -0,0 +1,319 @@
+from PyQt5.QtCore import QObject, pyqtSignal
+from PyQt5.QtCore import QCoreApplication as QC
+from google.protobuf.json_format import MessageToJson
+import uuid
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from .enums import Operator
+from .exprs import ExprLog
+
+class Rules(QObject):
+    rulesUpdated = pyqtSignal()
+
+    # Fields defined in the protobuf, to be used as constants on other parts.
+    FIELD_UUID = "UUID"
+    FIELD_ENABLED = "Enabled"
+    FIELD_TARGET = "Target"
+
+    def __init__(self, nodes):
+        QObject.__init__(self)
+        self._nodes = nodes
+        self.rulesUpdated.connect(self._cb_rules_updated)
+
+    def _cb_rules_updated(self):
+        pass
+
+    def add(self, addr, rule):
+        """Add a new rule to the corresponding table on the given node
+        """
+        node = self._nodes.get_node(addr)
+        if node == None or not 'firewall' in node:
+            return False, QC.translate("firewall", "rule not found by its ID.")
+        if self.is_duplicated(addr, rule):
+            return False, QC.translate("firewall", "duplicated.")
+
+        for sdx, n in enumerate(node['firewall'].SystemRules):
+            for cdx, c in enumerate(n.Chains):
+                if c.Name == rule.Name and \
+                        c.Hook == rule.Hook and \
+                        c.Table == rule.Table and \
+                        c.Family == rule.Family and \
+                        c.Type == rule.Type:
+                    node['firewall'].SystemRules[sdx].Chains[cdx].Rules.extend([rule.Rules[0]])
+                    node['fwrules'][rule.Rules[0].UUID] = rule
+                    self._nodes.add_fw_config(addr, node['firewall'])
+                    self._nodes.add_fw_rules(addr, node['fwrules'])
+
+                    self.rulesUpdated.emit()
+                    return True
+
+        return False, QC.translate("firewall", "firewall table/chain not properly configured.")
+
+    def insert(self, addr, rule, position=0):
+        """Insert a new rule to the corresponding table on the given node
+        """
+        node = self._nodes.get_node(addr)
+        if node == None or not 'firewall' in node:
+            return False, QC.translate("firewall", "this node doesn't have a firewall configuration, review it.")
+        if self.is_duplicated(addr, rule):
+            return False, QC.translate("firewall", "duplicated")
+
+        for sdx, n in enumerate(node['firewall'].SystemRules):
+            for cdx, c in enumerate(n.Chains):
+                if c.Name == rule.Name and \
+                        c.Hook == rule.Hook and \
+                        c.Table == rule.Table and \
+                        c.Family == rule.Family and \
+                        c.Type == rule.Type:
+                    if hasattr(node['firewall'].SystemRules[sdx].Chains[cdx].Rules, "insert"):
+                        node['firewall'].SystemRules[sdx].Chains[cdx].Rules.insert(int(position), rule.Rules[0])
+                    else:
+                        node['firewall'].SystemRules[sdx].Chains[cdx].Rules.extend([rule.Rules[0]])
+                    node['fwrules'][rule.Rules[0].UUID] = rule
+                    self._nodes.add_fw_config(addr, node['firewall'])
+                    self._nodes.add_fw_rules(addr, node['fwrules'])
+
+                    self.rulesUpdated.emit()
+                    return True, ""
+
+        return False, QC.translate("firewall", "firewall table/chain not properly configured.")
+
+
+    def update(self, addr, uuid, rule):
+        node = self._nodes.get_node(addr)
+        if node == None or not 'firewall' in node:
+            return False, QC.translate("firewall", "this node doesn't have a firewall configuration, review it.")
+        for sdx, n in enumerate(node['firewall'].SystemRules):
+            for cdx, c in enumerate(n.Chains):
+                for rdx, r in enumerate(c.Rules):
+                    if r.UUID == uuid:
+                        c.Rules[rdx].CopyFrom(rule.Rules[0])
+                        node['firewall'].SystemRules[sdx].Chains[cdx].Rules[rdx].CopyFrom(rule.Rules[0])
+                        self._nodes.add_fw_config(addr, node['firewall'])
+                        node['fwrules'][uuid] = rule
+                        self._nodes.add_fw_rules(addr, node['fwrules'])
+
+                        self.rulesUpdated.emit()
+                        return True, ""
+
+        return False, QC.translate("firewall", "rule not found by its ID.")
+
+    def get(self):
+        rules = []
+        for node in self._nodes.get_nodes():
+            node_rules = self.get_by_node(node)
+            rules += node_rules
+
+        return rules
+
+    def delete(self, addr, uuid):
+        node = self._nodes.get_node(addr)
+        if node == None or not 'firewall' in node:
+            return False, None
+        for sdx, n in enumerate(node['firewall'].SystemRules):
+            for cdx, c in enumerate(n.Chains):
+                for idx, r in enumerate(c.Rules):
+                    if r.UUID == uuid:
+                        del node['firewall'].SystemRules[sdx].Chains[cdx].Rules[idx]
+                        self._nodes.add_fw_config(addr, node['firewall'])
+                        if uuid in node['fwrules']:
+                            del node['fwrules'][uuid]
+                            self._nodes.add_fw_rules(addr, node['fwrules'])
+                        else:
+                            # raise Error("rules doesn't have UUID field")
+                            return False, None
+
+                        self.rulesUpdated.emit()
+                        return True, node['firewall']
+
+        return False, None
+
+    def get_by_node(self, addr):
+        rules = []
+        node = self._nodes.get_node(addr)
+        if node == None:
+            return rules
+        if not 'firewall' in node:
+            return rules
+        for u in node['firewall'].SystemRules:
+            for c in u.Chains:
+                for r in c.Rules:
+                    rules.append(Rules.to_array(addr, c, r))
+        return rules
+
+    def get_by_uuid(self, addr, uuid):
+        rules = []
+        node = self._nodes.get_node(addr)
+        if node == None:
+            return rules
+        if not 'firewall' in node:
+            return rules
+        for u in node['firewall'].SystemRules:
+            for c in u.Chains:
+                for r in c.Rules:
+                    if r.UUID == uuid:
+                        return r
+        return None
+
+    def swap(self, view, addr, uuid, old_pos, new_pos):
+        """
+        swap changes the order of 2 rows.
+
+        The list of rules is ordered from top to bottom: 0,1,2,3...
+        so a click on the down button sums +1, a click on the up button rest -1
+        """
+        node = self._nodes.get_node(addr)
+        if node == None:
+            return
+        if not 'firewall' in node:
+            return
+        for sdx, c in enumerate(node['firewall'].SystemRules):
+            for cdx, u in enumerate(c.Chains):
+                nrules = len(u.Rules)
+                for rdx, r in enumerate(u.Rules):
+                    # is the last rule
+                    if new_pos > nrules and new_pos < nrules:
+                        break
+                    if u.Rules[rdx].UUID == uuid:
+                        old_rule = u.Rules[old_pos]
+                        new_rule = ui_pb2.FwRule()
+                        new_rule.CopyFrom(u.Rules[new_pos])
+
+                        node['firewall'].SystemRules[sdx].Chains[cdx].Rules[new_pos].CopyFrom(old_rule)
+                        node['firewall'].SystemRules[sdx].Chains[cdx].Rules[old_pos].CopyFrom(new_rule)
+
+                        self._nodes.add_fw_config(addr, node['firewall'])
+                        #self._nodes.add_fw_rules(addr, node['fwrules'])
+
+                        self.rulesUpdated.emit()
+                        return True
+        return False
+
+    def is_duplicated(self, addr, orig_rule):
+        # we need to duplicate the rule, otherwise we'd modify the UUID of the
+        # orig rule.
+        temp_c = ui_pb2.FwChain()
+        temp_c.CopyFrom(orig_rule)
+        # the UUID will be different, so zero it out.
+        # but keep a copy of the original one.
+        orig_uuid = temp_c.Rules[0].UUID
+        temp_c.Rules[0].UUID = ""
+        node = self._nodes.get_node(addr)
+        if node == None:
+            return False
+        if not 'firewall' in node:
+            return False
+        for n in node['firewall'].SystemRules:
+            for c in n.Chains:
+                if c.Name == temp_c.Name and \
+                        c.Hook == temp_c.Hook and \
+                        c.Table == temp_c.Table and \
+                        c.Family == temp_c.Family and \
+                        c.Type == temp_c.Type:
+                    for rdx, r in enumerate(c.Rules):
+                        uuid = c.Rules[rdx].UUID
+                        c.Rules[rdx].UUID = ""
+                        is_equal = (c.Rules[rdx].SerializeToString() == temp_c.Rules[0].SerializeToString() or orig_uuid == uuid)
+                        c.Rules[rdx].UUID = uuid
+
+                        if is_equal:
+                            return True
+
+        return False
+
+    @staticmethod
+    def new(
+            enabled=True,
+            _uuid="",
+            description="",
+            expressions=None,
+            target="",
+            target_parms=""
+            ):
+        rule = ui_pb2.FwRule()
+        if _uuid == "":
+            rule.UUID = str(uuid.uuid1())
+        else:
+            rule.UUID = _uuid
+        rule.Enabled = enabled
+        rule.Description = description
+        if expressions != None:
+            rule.Expressions.extend([expressions])
+        rule.Target = target
+        rule.TargetParameters = target_parms
+
+        return rule
+
+    @staticmethod
+    def new_flat(c, r):
+        """Create a new "flat" rule from a hierarchical one.
+        Transform from:
+            {
+             xx:
+                 {
+                   yy: {
+        to:
+            {xx:, yy}
+        """
+
+        chain = ui_pb2.FwChain()
+        chain.CopyFrom(c)
+        del chain.Rules[:]
+        chain.Rules.extend([r])
+
+        return chain
+
+    @staticmethod
+    def to_dict(sysRules):
+        """Transform json/protobuf struct to flat structure.
+        This is the default format used to find rules in the table view.
+        """
+        rules={}
+        for s in sysRules:
+            for c in s.Chains:
+                if len(c.Rules) == 0:
+                    continue
+                for r in c.Rules:
+                    rules[r.UUID] = Rules.new_flat(c, r)
+
+        return rules
+
+    @staticmethod
+    def to_json(rule):
+        try:
+            return MessageToJson(rule)
+        except:
+            return None
+
+    @staticmethod
+    def to_array(addr, chain, rule):
+        cols = []
+        cols.append(rule.UUID)
+        cols.append(addr)
+        cols.append(chain.Name)
+        cols.append(chain.Table)
+        cols.append(chain.Family)
+        cols.append(chain.Hook)
+        cols.append(str(rule.Enabled))
+        cols.append(rule.Description)
+        exprs = ""
+        for e in rule.Expressions:
+            exprs += "{0} {1}".format(
+                e.Statement.Name,
+                "".join(
+                    [
+                        "{0} {1}{2} ".format(
+                            h.Key,
+                            e.Statement.Op + " " if e.Statement.Op != Operator.EQUAL.value else "",
+                            "\"{0}\"".format(h.Value) if h.Key == ExprLog.PREFIX.value else h.Value
+                        ) for h in e.Statement.Values
+                    ]
+                )
+            )
+        cols.append(exprs)
+        cols.append(rule.Target)
+        cols.append(rule.TargetParameters)
+
+        return cols
diff --git a/ui/opensnitch/firewall/utils.py b/ui/opensnitch/firewall/utils.py
new file mode 100644 (file)
index 0000000..3e6882a
--- /dev/null
@@ -0,0 +1,24 @@
+
+from google.protobuf import __version__ as protobuf_version
+from .enums import *
+
+class Utils():
+
+    @staticmethod
+    def isExprPort(value):
+        """Return true if the value is valid for a port based rule:
+            nft add rule ... tcp dport 22 accept
+        """
+        return value == Statements.TCP.value or \
+                value == Statements.UDP.value or \
+                value == Statements.UDPLITE.value or \
+                value == Statements.SCTP.value or \
+                value == Statements.DCCP.value
+
+    @staticmethod
+    def isProtobufSupported():
+        """
+        The protobuf operations append() and insert() were introduced on 3.8.0 version.
+        """
+        vparts = protobuf_version.split(".")
+        return int(vparts[0]) >= 3 and int(vparts[1]) >= 8
diff --git a/ui/opensnitch/nodes.py b/ui/opensnitch/nodes.py
new file mode 100644 (file)
index 0000000..af52638
--- /dev/null
@@ -0,0 +1,397 @@
+from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
+from queue import Queue
+from datetime import datetime
+import time
+import json
+
+from opensnitch.database import Database
+from opensnitch.config import Config
+from opensnitch.utils import NetworkInterfaces
+from opensnitch.rules import Rules
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+class Nodes(QObject):
+    __instance = None
+    nodesUpdated = pyqtSignal(int) # total
+
+    LOG_TAG = "[Nodes]: "
+    ONLINE = "\u2713 online"
+    OFFLINE = "\u2613 offline"
+    WARNING = "\u26a0"
+
+    @staticmethod
+    def instance():
+        if Nodes.__instance == None:
+            Nodes.__instance = Nodes()
+        return Nodes.__instance
+
+    def __init__(self):
+        QObject.__init__(self)
+        self._db = Database.instance()
+        self._rules = Rules()
+        self._nodes = {}
+        self._notifications_sent = {}
+        self._interfaces = NetworkInterfaces()
+
+    def count(self):
+        return len(self._nodes)
+
+    def add(self, _peer, client_config=None):
+        try:
+            proto, addr = self.get_addr(_peer)
+            peer = proto+":"+addr
+            if peer not in self._nodes:
+                self._nodes[peer] = {
+                        'notifications': Queue(),
+                        'online':        True,
+                        'last_seen':     datetime.now()
+                        }
+            else:
+                self._nodes[peer]['last_seen'] = datetime.now()
+
+            self._nodes[peer]['online'] = True
+            self.add_data(peer, client_config)
+            self.update(peer)
+
+            self.nodesUpdated.emit(self.count())
+
+            return self._nodes[peer], peer
+
+        except Exception as e:
+            print(self.LOG_TAG, "exception adding/updating node: ", e, "addr:", addr, "config:", client_config)
+
+        return None, None
+
+    def add_data(self, addr, client_config):
+        if client_config != None:
+            self._nodes[addr]['data'] = self.get_client_config(client_config)
+            self.add_fw_config(addr, client_config.systemFirewall)
+            self._rules.add_rules(addr, client_config.rules)
+
+    def add_fw_config(self, addr, fwconfig):
+        self._nodes[addr]['firewall'] = fwconfig
+
+    def add_fw_rules(self, addr, fwconfig):
+        self._nodes[addr]['fwrules'] = fwconfig
+
+    def add_rule(self, time, node, name, description, enabled, precedence, nolog, action, duration, op_type, op_sensitive, op_operand, op_data, created):
+        # don't add rule if the user has selected to exclude temporary
+        # rules
+        if duration in Config.RULES_DURATION_FILTER:
+            return
+
+        self._rules.add(time, node, name, description, enabled, precedence, nolog, action, duration, op_type, op_sensitive, op_operand, op_data, created)
+
+    def add_rules(self, addr, rules):
+        try:
+            self._rules.add_rules(addr, rules)
+        except Exception as e:
+            print(self.LOG_TAG + " exception adding node to db: ", e)
+
+    def delete_rule(self, rule_name, addr, callback):
+        deleted_rule = self._rules.delete(rule_name, addr, callback)
+        if deleted_rule == None:
+            print(self.LOG_TAG, "error deleting rule", rule_name)
+            return None, None
+
+        noti = ui_pb2.Notification(type=ui_pb2.DELETE_RULE, rules=[deleted_rule])
+        if addr != None:
+            nid = self.send_notification(addr, noti, callback)
+        else:
+            nid = self.send_notifications(noti, callback)
+
+        return nid, noti
+
+    def delete_rule_by_field(self, field, values):
+        return self._rules.delete_by_field(field, values)
+
+    def rule_to_json(self, addr, rule_name):
+        return self._rules.rule_to_json(addr, rule_name)
+
+    def export_rule(self, addr, rule_name, outdir):
+        return self._rules.export_rule(addr, rule_name, outdir)
+
+    def export_rules(self, addr, outdir):
+        return self._rules.export_rules(addr, outdir)
+
+    def import_rules(self, addr=None, rulesdir="", callback=None):
+        rules_list = self._rules.import_rules(rulesdir)
+        if rules_list == None:
+            return None, None, None
+
+        notif = ui_pb2.Notification(
+                id=int(str(time.time()).replace(".", "")),
+                type=ui_pb2.CHANGE_RULE,
+                data="",
+                rules=rules_list)
+
+        if addr != None:
+            nid = self.send_notification(addr, notif, callback)
+        else:
+            nid = self.send_notifications(notif, callback)
+
+        return nid, notif, rules_list
+
+    def update_rule_time(self, time, rule_name, addr):
+        self._rules.update_time(time, rule_name, addr)
+
+    def delete_all(self):
+        self.send_notifications(None)
+        self._nodes = {}
+        self.nodesUpdated.emit(self.count())
+
+    def delete(self, peer):
+        try:
+            proto, addr = self.get_addr(peer)
+            addr = "%s:%s" % (proto, addr)
+            # Force the node to get one new item from queue,
+            # in order to loop and exit.
+            self._nodes[addr]['notifications'].put(None)
+        except:
+            addr = peer
+
+        if addr in self._nodes:
+            del self._nodes[addr]
+            self.nodesUpdated.emit(self.count())
+
+    def get(self):
+        return self._nodes
+
+    def get_node(self, addr):
+        try:
+            return self._nodes[addr]
+        except Exception as e:
+            return None
+
+    def get_nodes(self):
+        return self._nodes
+
+    def get_node_config(self, addr):
+        try:
+            if addr not in self._nodes:
+                return None
+            return self._nodes[addr]['data'].config
+        except Exception as e:
+            print(self.LOG_TAG + " exception get_node_config(): ", e)
+            return None
+
+    def get_client_config(self, client_config):
+        try:
+            node_config = json.loads(client_config.config)
+            if 'LogLevel' not in node_config:
+                node_config['LogLevel'] = 1
+                client_config.config = json.dumps(node_config)
+        except Exception as e:
+            print(self.LOG_TAG, "exception parsing client config", e)
+
+        return client_config
+
+    def get_addr(self, peer):
+        try:
+            peer = peer.split(":")
+            # WA for backward compatibility
+            if peer[0] == "unix" and peer[1] == "":
+                peer[1] = "/local"
+            return peer[0], peer[1]
+        except:
+            print(self.LOG_TAG, "get_addr() error getting addr:", peer)
+            return peer
+
+    def is_local(self, addr):
+        if addr.startswith("unix"):
+            return True
+
+        if addr.startswith("ipv4") or addr.startswith("ipv6"):
+            ifaces = self._interfaces.list()
+            for name in ifaces:
+                if ifaces[name] in addr:
+                    return True
+
+        return False
+
+    def get_notifications(self):
+        notlist = []
+        try:
+            for c in self._nodes:
+                if self._nodes[c]['online'] == False:
+                    continue
+                if self._nodes[c]['notifications'].empty():
+                    continue
+                notif = self._nodes[c]['notifications'].get(False)
+                if notif != None:
+                    self._nodes[c]['notifications'].task_done()
+                    notlist.append(notif)
+        except Exception as e:
+            print(self.LOG_TAG + " exception get_notifications(): ", e)
+
+        return notlist
+
+    def save_node_config(self, addr, config):
+        try:
+            self._nodes[addr]['data'].config = config
+        except Exception as e:
+            print(self.LOG_TAG + " exception saving node config: ", e, addr, config)
+
+    def save_nodes_config(self, config):
+        try:
+            for c in self._nodes:
+                self._nodes[c]['data'].config = config
+        except Exception as e:
+            print(self.LOG_TAG + " exception saving nodes config: ", e, config)
+
+    def change_node_config(self, addr, config, _callback):
+        _cfg = json.dumps(config, indent="    ")
+        notif = ui_pb2.Notification(
+            id=int(str(time.time()).replace(".", "")),
+            type=ui_pb2.CHANGE_CONFIG,
+            data=_cfg,
+            rules=[])
+        self.save_node_config(addr, _cfg)
+        return self.send_notification(addr, notif, _callback), notif
+
+    def start_interception(self, _addr=None, _callback=None):
+        return self.firewall(not_type=ui_pb2.ENABLE_INTERCEPTION, addr=_addr, callback=_callback)
+
+    def stop_interception(self, _addr=None, _callback=None):
+        return self.firewall(not_type=ui_pb2.DISABLE_INTERCEPTION, addr=_addr, callback=_callback)
+
+    def firewall(self, not_type=ui_pb2.ENABLE_INTERCEPTION, addr=None, callback=None):
+        noti = ui_pb2.Notification(clientName="", serverName="", type=not_type, data="", rules=[])
+        if addr == None:
+            nid = self.send_notifications(noti, callback)
+        else:
+            nid = self.send_notification(addr, noti, callback)
+
+        return nid, noti
+
+    def send_notification(self, addr, notification, callback_signal=None):
+        try:
+            notification.id = int(str(time.time()).replace(".", ""))
+            if addr not in self._nodes:
+                # FIXME: the reply is sent before we return the notification id
+                if callback_signal != None:
+                    callback_signal.emit(
+                        ui_pb2.NotificationReply(
+                            id=notification.id,
+                            code=ui_pb2.ERROR,
+                            data="node not connected: {0}".format(addr)
+                        )
+                    )
+                return notification.id
+
+            self._notifications_sent[notification.id] = {
+                    'callback': callback_signal,
+                    'type': notification.type
+                    }
+
+            self._nodes[addr]['notifications'].put(notification)
+        except Exception as e:
+            print(self.LOG_TAG + " exception sending notification: ", e, addr, notification)
+            if callback_signal != None:
+                callback_signal.emit(
+                    ui_pb2.NotificationReply(
+                        id=notification.id,
+                        code=ui_pb2.ERROR,
+                        data="Notification not sent ({0}):<br>{1}".format(addr, e)
+                    )
+                )
+
+        return notification.id
+
+    def send_notifications(self, notification, callback_signal=None):
+        """
+        Enqueues a notification to the clients queue.
+        It'll be retrieved and delivered by get_notifications
+        """
+        try:
+            notification.id = int(str(time.time()).replace(".", ""))
+            for c in self._nodes:
+                self._nodes[c]['notifications'].put(notification)
+            self._notifications_sent[notification.id] = {
+                    'callback': callback_signal,
+                    'type': notification.type
+                    }
+        except Exception as e:
+            print(self.LOG_TAG + " exception sending notifications: ", e, notification)
+
+        return notification.id
+
+    def reply_notification(self, addr, reply):
+        try:
+            if reply == None:
+                print(self.LOG_TAG, " reply notification None")
+                return
+
+            if reply.id not in self._notifications_sent:
+                print(self.LOG_TAG, " reply notification not in the list:", reply.id)
+                return
+
+            if self._notifications_sent[reply.id] == None:
+                print(self.LOG_TAG, " reply notification body empty:", reply.id)
+                return
+
+            if self._notifications_sent[reply.id]['callback'] != None:
+                self._notifications_sent[reply.id]['callback'].emit(reply)
+
+            # delete only one-time notifications
+            # we need the ID of streaming notifications from the server
+            # (monitor_process for example) to keep track of the data sent to us.
+            if self._notifications_sent[reply.id]['type'] != ui_pb2.MONITOR_PROCESS:
+                del self._notifications_sent[reply.id]
+        except Exception as e:
+            print(self.LOG_TAG, "notification exception:", e)
+
+    def stop_notifications(self):
+        """Send a dummy notification to force Notifications class to exit.
+        """
+        exit_noti = ui_pb2.Notification(clientName="", serverName="", type=0, data="", rules=[])
+        self.send_notifications(exit_noti)
+
+    def update(self, peer, status=ONLINE):
+        try:
+            proto, addr = self.get_addr(peer)
+            self._db.update("nodes",
+                    "hostname=?,version=?,last_connection=?,status=?",
+                    (
+                        self._nodes[proto+":"+addr]['data'].name,
+                        self._nodes[proto+":"+addr]['data'].version,
+                        datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+                        status,
+                        "{0}:{1}".format(proto, addr)),
+                        "addr=?"
+                    )
+        except Exception as e:
+            print(self.LOG_TAG + " exception updating DB: ", e, peer)
+
+    def update_all(self, status=OFFLINE):
+        try:
+            for peer in self._nodes:
+                self._db.update("nodes",
+                        "hostname=?,version=?,last_connection=?,status=?",
+                        (
+                            self._nodes[peer]['data'].name,
+                            self._nodes[peer]['data'].version,
+                            datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+                            status,
+                            peer),
+                            "addr=?"
+                        )
+        except Exception as e:
+            print(self.LOG_TAG + " exception updating nodes: ", e)
+
+    def reset_status(self):
+        try:
+            self._db.update("nodes", "status=?", (self.OFFLINE,))
+        except Exception as e:
+            print(self.LOG_TAG + " exception resetting nodes status: ", e)
+
+    def reload_fw(self, addr, fw_config, callback):
+        notif = ui_pb2.Notification(
+                id=int(str(time.time()).replace(".", "")),
+                type=ui_pb2.RELOAD_FW_RULES,
+                sysFirewall=fw_config
+        )
+        nid = self.send_notification(addr, notif, callback)
+        return nid, notif
diff --git a/ui/opensnitch/notifications.py b/ui/opensnitch/notifications.py
new file mode 100644 (file)
index 0000000..461209b
--- /dev/null
@@ -0,0 +1,132 @@
+from PyQt5.QtCore import QCoreApplication as QC
+import os
+from opensnitch.utils import Utils
+from opensnitch.config import Config
+
+class DesktopNotifications():
+    """DesktopNotifications display informative pop-ups using the system D-Bus.
+    The notifications are handled and configured by the system.
+
+    The notification daemon also decides where to show the notifications, as well
+    as how to group them.
+
+    The body of a notification supports markup (if the implementation supports it):
+        https://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html#markup
+    Basically: <a>, <u>, <b>, <i> and <img>. New lines can be added with the regular \n.
+
+    It also support actions (buttons).
+
+    https://notify2.readthedocs.io/en/latest/
+    """
+
+    _cfg = Config.init()
+
+    # list of hints:
+    # https://people.gnome.org/~mccann/docs/notification-spec/notification-spec-latest.html#hints
+    HINT_DESKTOP_ENTRY = "desktop-entry"
+    CATEGORY_NETWORK = "network"
+
+    EXPIRES_DEFAULT = 0
+    NEVER_EXPIRES = -1
+
+    URGENCY_LOW = 0
+    URGENCY_NORMAL = 1
+    URGENCY_CRITICAL = 2
+
+    # must be a string
+    ACTION_ID_OPEN = "action-open"
+    ACTION_ID_ALLOW = "action-allow"
+    ACTION_ID_DENY = "action-deny"
+
+    def __init__(self):
+        self.ACTION_OPEN = QC.translate("popups", "Open")
+        self.ACTION_ALLOW = QC.translate("popups", "Allow")
+        self.ACTION_DENY = QC.translate("popups", "Deny")
+        self.IS_LIBNOTIFY_AVAILABLE = True
+        self.DOES_SUPPORT_ACTIONS = True
+
+        try:
+            import notify2
+            self.ntf2 = notify2
+            mloop = 'glib'
+
+            # First try to initialise the D-Bus connection with the given
+            # mainloop.
+            # If it fails, we'll try to initialise it without it.
+            try:
+                self.ntf2.init("opensnitch", mainloop=mloop)
+            except Exception:
+                self.DOES_SUPPORT_ACTIONS = False
+                self.ntf2.init("opensnitch")
+
+                # usually because dbus mainloop is not initiated, specially
+                # with 'qt'
+                # FIXME: figure out how to init it, or how to connect to an
+                # existing session.
+                print("DesktopNotifications(): system doesn't support actions. Available capabilities:")
+                print(self.ntf2.get_server_caps())
+
+
+            # Example: ['actions', 'action-icons', 'body', 'body-markup', 'icon-static', 'persistence', 'sound']
+            if ('actions' not in self.ntf2.get_server_caps()):
+                self.DOES_SUPPORT_ACTIONS = False
+
+        except Exception as e:
+            print("DesktopNotifications not available (install python3-notify2):", e)
+            self.IS_LIBNOTIFY_AVAILABLE = False
+
+    def is_available(self):
+        return self.IS_LIBNOTIFY_AVAILABLE
+
+    def are_enabled(self):
+        return self._cfg.getBool(Config.NOTIFICATIONS_ENABLED, True)
+
+    def support_actions(self):
+        """Returns true if the notifications daemon support actions(buttons).
+        This depends on 2 factors:
+            - If the notification server actually supports it (get_server_caps()).
+            - If there's a dbus instance running.
+        """
+        return self.DOES_SUPPORT_ACTIONS
+
+    def show(self, title, body, icon="dialog-information", urgency=URGENCY_NORMAL, callback=None):
+        try:
+            ntf = self.ntf2.Notification(title, body, icon)
+
+            ntf.set_urgency(urgency)
+            ntf.set_category(self.CATEGORY_NETWORK)
+            # used to display our app icon and name.
+            # Note: setting this Hint causes some DEs to call opensnitch_ui.desktop file,
+            # that as of today, kills and relaunches the current opensnitch-ui process.
+            #ntf.set_hint(self.HINT_DESKTOP_ENTRY, "opensnitch_ui")
+            if self.DOES_SUPPORT_ACTIONS and callback != None:
+                ntf.add_action(self.ACTION_ID_OPEN, self.ACTION_OPEN, callback)
+            ntf.show()
+        except Exception as e:
+            print("[notifications] show() exception:", e)
+            raise Exception("[notifications] show() exception:", e)
+
+    # TODO:
+    #  - construct a rule with the default configured parameters.
+    #  - create a common dialogs/prompt.py:_send_rule(), maybe in utils.py
+    def ask(self, connection, timeout, callback):
+        c = connection
+        title = QC.translate("popups", "New outgoing connection")
+        body = c.process_path + "\n"
+        body = body + QC.translate("popups", "is connecting to <b>%s</b> on %s port %d") % ( \
+            c.dst_host or c.dst_ip,
+            c.protocol.upper(),
+            c.dst_port )
+
+        ntf = self.ntf2.Notification(title, body, "dialog-warning")
+        timeout = self._cfg.getInt(Config.DEFAULT_TIMEOUT_KEY, 15)
+        ntf.set_timeout(timeout * 1000)
+        ntf.timeout = timeout * 1000
+        if self.DOES_SUPPORT_ACTIONS:
+            ntf.set_urgency(self.ntf2.URGENCY_CRITICAL)
+            ntf.add_action(self.ACTION_ID_ALLOW, self.ACTION_ALLOW, callback, connection)
+            ntf.add_action(self.ACTION_ID_DENY, self.ACTION_DENY, callback, connection)
+            #ntf.add_action("open-gui", QC.translate("popups", "View"), callback, connection)
+        ntf.set_category(self.CATEGORY_NETWORK)
+        ntf.set_hint(self.HINT_DESKTOP_ENTRY, "opensnitch_ui")
+        ntf.show()
diff --git a/ui/opensnitch/proto/__init__.py b/ui/opensnitch/proto/__init__.py
new file mode 100644 (file)
index 0000000..c712d44
--- /dev/null
@@ -0,0 +1,57 @@
+#   Copyright (C) 2018      Simone Margaritelli
+#                 2019-2025 Gustavo Iñiguez Goia
+#
+#   This file is part of OpenSnitch.
+#
+#   OpenSnitch is free software: you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation, either version 3 of the License, or
+#   (at your option) any later version.
+#
+#   OpenSnitch is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with OpenSnitch.  If not, see <http://www.gnu.org/licenses/>.
+
+from packaging.version import Version
+import importlib
+from opensnitch.utils import Versions
+
+# Protobuffers compiled with protobuf < 3.20.0 are incompatible with
+# protobuf >= 4.0.0
+# https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems#gui-does-not-show-up
+#
+# In order to solve this issue, we provide several protobuffers:
+# proto.ui_pb2* for protobuf >= 4.0.0
+# proto.pre3200.ui_pb2* for protobuf >= 3.6.0 and < 3.20.0
+#
+# To avoid import errors, each protobuffer must be placed in its own directory,
+# and the name of the protobuffer files must be named with the syntax
+# <prefix>_pb2.py/<prefix>_pb2_grpc.py:
+#  ui_pb2.py and ui_pb2_grpc.py
+
+default_pb = "opensnitch.proto.ui_pb2"
+default_grpc = "opensnitch.proto.ui_pb2_grpc"
+old_pb = "opensnitch.proto.pre3200.ui_pb2"
+old_grpc = "opensnitch.proto.pre3200.ui_pb2_grpc"
+
+def import_():
+    """load the protobuffer needed based on the grpc and protobuffer version
+    installed in the system.
+    """
+    try:
+        gui_version, grpc_version, proto_version = Versions.get()
+        proto_ver = default_pb
+        grpc_ver = default_grpc
+
+        if Version(proto_version) < Version("3.20.0"):
+            proto_ver = old_pb
+            grpc_ver = old_grpc
+
+        return importlib.import_module(proto_ver), importlib.import_module(grpc_ver)
+    except Exception as e:
+        print("error importing protobuffer: ", repr(e))
+        return importlib.import_module(default_pb, default_grpc)
diff --git a/ui/opensnitch/proto/pre3200/ui_pb2.py b/ui/opensnitch/proto/pre3200/ui_pb2.py
new file mode 100644 (file)
index 0000000..f13ec65
--- /dev/null
@@ -0,0 +1,2247 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: ui.proto
+
+import sys
+_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1'))
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+  name='ui.proto',
+  package='protocol',
+  syntax='proto3',
+  serialized_options=_b('Z3github.com/evilsocket/opensnitch/daemon/ui/protocol'),
+  serialized_pb=_b('\n\x08ui.proto\x12\x08protocol\"\xcb\x04\n\x05\x41lert\x12\n\n\x02id\x18\x01 \x01(\x04\x12\"\n\x04type\x18\x02 \x01(\x0e\x32\x14.protocol.Alert.Type\x12&\n\x06\x61\x63tion\x18\x03 \x01(\x0e\x32\x16.protocol.Alert.Action\x12*\n\x08priority\x18\x04 \x01(\x0e\x32\x18.protocol.Alert.Priority\x12\"\n\x04what\x18\x05 \x01(\x0e\x32\x14.protocol.Alert.What\x12\x0e\n\x04text\x18\x06 \x01(\tH\x00\x12!\n\x04proc\x18\x08 \x01(\x0b\x32\x11.protocol.ProcessH\x00\x12$\n\x04\x63onn\x18\t \x01(\x0b\x32\x14.protocol.ConnectionH\x00\x12\x1e\n\x04rule\x18\n \x01(\x0b\x32\x0e.protocol.RuleH\x00\x12\"\n\x06\x66wrule\x18\x0b \x01(\x0b\x32\x10.protocol.FwRuleH\x00\")\n\x08Priority\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02\"(\n\x04Type\x12\t\n\x05\x45RROR\x10\x00\x12\x0b\n\x07WARNING\x10\x01\x12\x08\n\x04INFO\x10\x02\"2\n\x06\x41\x63tion\x12\x08\n\x04NONE\x10\x00\x12\x0e\n\nSHOW_ALERT\x10\x01\x12\x0e\n\nSAVE_TO_DB\x10\x02\"l\n\x04What\x12\x0b\n\x07GENERIC\x10\x00\x12\x10\n\x0cPROC_MONITOR\x10\x01\x12\x0c\n\x08\x46IREWALL\x10\x02\x12\x0e\n\nCONNECTION\x10\x03\x12\x08\n\x04RULE\x10\x04\x12\x0b\n\x07NETLINK\x10\x05\x12\x10\n\x0cKERNEL_EVENT\x10\x06\x42\x06\n\x04\x64\x61ta\"\x19\n\x0bMsgResponse\x12\n\n\x02id\x18\x01 \x01(\x04\"o\n\x05\x45vent\x12\x0c\n\x04time\x18\x01 \x01(\t\x12(\n\nconnection\x18\x02 \x01(\x0b\x32\x14.protocol.Connection\x12\x1c\n\x04rule\x18\x03 \x01(\x0b\x32\x0e.protocol.Rule\x12\x10\n\x08unixnano\x18\x04 \x01(\x03\"\xd3\x06\n\nStatistics\x12\x16\n\x0e\x64\x61\x65mon_version\x18\x01 \x01(\t\x12\r\n\x05rules\x18\x02 \x01(\x04\x12\x0e\n\x06uptime\x18\x03 \x01(\x04\x12\x15\n\rdns_responses\x18\x04 \x01(\x04\x12\x13\n\x0b\x63onnections\x18\x05 \x01(\x04\x12\x0f\n\x07ignored\x18\x06 \x01(\x04\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x07 \x01(\x04\x12\x0f\n\x07\x64ropped\x18\x08 \x01(\x04\x12\x11\n\trule_hits\x18\t \x01(\x04\x12\x13\n\x0brule_misses\x18\n \x01(\x04\x12\x33\n\x08\x62y_proto\x18\x0b \x03(\x0b\x32!.protocol.Statistics.ByProtoEntry\x12\x37\n\nby_address\x18\x0c \x03(\x0b\x32#.protocol.Statistics.ByAddressEntry\x12\x31\n\x07\x62y_host\x18\r \x03(\x0b\x32 .protocol.Statistics.ByHostEntry\x12\x31\n\x07\x62y_port\x18\x0e \x03(\x0b\x32 .protocol.Statistics.ByPortEntry\x12/\n\x06\x62y_uid\x18\x0f \x03(\x0b\x32\x1f.protocol.Statistics.ByUidEntry\x12=\n\rby_executable\x18\x10 \x03(\x0b\x32&.protocol.Statistics.ByExecutableEntry\x12\x1f\n\x06\x65vents\x18\x11 \x03(\x0b\x32\x0f.protocol.Event\x1a.\n\x0c\x42yProtoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a\x30\n\x0e\x42yAddressEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a-\n\x0b\x42yHostEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a-\n\x0b\x42yPortEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a,\n\nByUidEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a\x33\n\x11\x42yExecutableEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\">\n\x0bPingRequest\x12\n\n\x02id\x18\x01 \x01(\x04\x12#\n\x05stats\x18\x02 \x01(\x0b\x32\x14.protocol.Statistics\"\x17\n\tPingReply\x12\n\n\x02id\x18\x01 \x01(\x04\"\x89\x02\n\x07Process\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12\x0c\n\x04ppid\x18\x02 \x01(\x04\x12\x0b\n\x03uid\x18\x03 \x01(\x04\x12\x0c\n\x04\x63omm\x18\x04 \x01(\t\x12\x0c\n\x04path\x18\x05 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x06 \x03(\t\x12\'\n\x03\x65nv\x18\x07 \x03(\x0b\x32\x1a.protocol.Process.EnvEntry\x12\x0b\n\x03\x63wd\x18\x08 \x01(\t\x12\x10\n\x08io_reads\x18\t \x01(\x04\x12\x11\n\tio_writes\x18\n \x01(\x04\x12\x11\n\tnet_reads\x18\x0b \x01(\x04\x12\x12\n\nnet_writes\x18\x0c \x01(\x04\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc8\x02\n\nConnection\x12\x10\n\x08protocol\x18\x01 \x01(\t\x12\x0e\n\x06src_ip\x18\x02 \x01(\t\x12\x10\n\x08src_port\x18\x03 \x01(\r\x12\x0e\n\x06\x64st_ip\x18\x04 \x01(\t\x12\x10\n\x08\x64st_host\x18\x05 \x01(\t\x12\x10\n\x08\x64st_port\x18\x06 \x01(\r\x12\x0f\n\x07user_id\x18\x07 \x01(\r\x12\x12\n\nprocess_id\x18\x08 \x01(\r\x12\x14\n\x0cprocess_path\x18\t \x01(\t\x12\x13\n\x0bprocess_cwd\x18\n \x01(\t\x12\x14\n\x0cprocess_args\x18\x0b \x03(\t\x12\x39\n\x0bprocess_env\x18\x0c \x03(\x0b\x32$.protocol.Connection.ProcessEnvEntry\x1a\x31\n\x0fProcessEnvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"l\n\x08Operator\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07operand\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\t\x12\x11\n\tsensitive\x18\x04 \x01(\x08\x12 \n\x04list\x18\x05 \x03(\x0b\x32\x12.protocol.Operator\"\xb6\x01\n\x04Rule\x12\x0f\n\x07\x63reated\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\x12\x12\n\nprecedence\x18\x05 \x01(\x08\x12\r\n\x05nolog\x18\x06 \x01(\x08\x12\x0e\n\x06\x61\x63tion\x18\x07 \x01(\t\x12\x10\n\x08\x64uration\x18\x08 \x01(\t\x12$\n\x08operator\x18\t \x01(\x0b\x32\x12.protocol.Operator\"-\n\x0fStatementValues\x12\x0b\n\x03Key\x18\x01 \x01(\t\x12\r\n\x05Value\x18\x02 \x01(\t\"P\n\tStatement\x12\n\n\x02Op\x18\x01 \x01(\t\x12\x0c\n\x04Name\x18\x02 \x01(\t\x12)\n\x06Values\x18\x03 \x03(\x0b\x32\x19.protocol.StatementValues\"5\n\x0b\x45xpressions\x12&\n\tStatement\x18\x01 \x01(\x0b\x32\x13.protocol.Statement\"\xd6\x01\n\x06\x46wRule\x12\r\n\x05Table\x18\x01 \x01(\t\x12\r\n\x05\x43hain\x18\x02 \x01(\t\x12\x0c\n\x04UUID\x18\x03 \x01(\t\x12\x0f\n\x07\x45nabled\x18\x04 \x01(\x08\x12\x10\n\x08Position\x18\x05 \x01(\x04\x12\x13\n\x0b\x44\x65scription\x18\x06 \x01(\t\x12\x12\n\nParameters\x18\x07 \x01(\t\x12*\n\x0b\x45xpressions\x18\x08 \x03(\x0b\x32\x15.protocol.Expressions\x12\x0e\n\x06Target\x18\t \x01(\t\x12\x18\n\x10TargetParameters\x18\n \x01(\t\"\x95\x01\n\x07\x46wChain\x12\x0c\n\x04Name\x18\x01 \x01(\t\x12\r\n\x05Table\x18\x02 \x01(\t\x12\x0e\n\x06\x46\x61mily\x18\x03 \x01(\t\x12\x10\n\x08Priority\x18\x04 \x01(\t\x12\x0c\n\x04Type\x18\x05 \x01(\t\x12\x0c\n\x04Hook\x18\x06 \x01(\t\x12\x0e\n\x06Policy\x18\x07 \x01(\t\x12\x1f\n\x05Rules\x18\x08 \x03(\x0b\x32\x10.protocol.FwRule\"M\n\x08\x46wChains\x12\x1e\n\x04Rule\x18\x01 \x01(\x0b\x32\x10.protocol.FwRule\x12!\n\x06\x43hains\x18\x02 \x03(\x0b\x32\x11.protocol.FwChain\"X\n\x0bSysFirewall\x12\x0f\n\x07\x45nabled\x18\x01 \x01(\x08\x12\x0f\n\x07Version\x18\x02 \x01(\r\x12\'\n\x0bSystemRules\x18\x03 \x03(\x0b\x32\x12.protocol.FwChains\"\xc4\x01\n\x0c\x43lientConfig\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x19\n\x11isFirewallRunning\x18\x04 \x01(\x08\x12\x0e\n\x06\x63onfig\x18\x05 \x01(\t\x12\x10\n\x08logLevel\x18\x06 \x01(\r\x12\x1d\n\x05rules\x18\x07 \x03(\x0b\x32\x0e.protocol.Rule\x12-\n\x0esystemFirewall\x18\x08 \x01(\x0b\x32\x15.protocol.SysFirewall\"\xbb\x01\n\x0cNotification\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nclientName\x18\x02 \x01(\t\x12\x12\n\nserverName\x18\x03 \x01(\t\x12\x1e\n\x04type\x18\x04 \x01(\x0e\x32\x10.protocol.Action\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\t\x12\x1d\n\x05rules\x18\x06 \x03(\x0b\x32\x0e.protocol.Rule\x12*\n\x0bsysFirewall\x18\x07 \x01(\x0b\x32\x15.protocol.SysFirewall\"\\\n\x11NotificationReply\x12\n\n\x02id\x18\x01 \x01(\x04\x12-\n\x04\x63ode\x18\x02 \x01(\x0e\x32\x1f.protocol.NotificationReplyCode\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\t*\xa5\x02\n\x06\x41\x63tion\x12\x08\n\x04NONE\x10\x00\x12\x17\n\x13\x45NABLE_INTERCEPTION\x10\x01\x12\x18\n\x14\x44ISABLE_INTERCEPTION\x10\x02\x12\x13\n\x0f\x45NABLE_FIREWALL\x10\x03\x12\x14\n\x10\x44ISABLE_FIREWALL\x10\x04\x12\x13\n\x0fRELOAD_FW_RULES\x10\x05\x12\x11\n\rCHANGE_CONFIG\x10\x06\x12\x0f\n\x0b\x45NABLE_RULE\x10\x07\x12\x10\n\x0c\x44ISABLE_RULE\x10\x08\x12\x0f\n\x0b\x44\x45LETE_RULE\x10\t\x12\x0f\n\x0b\x43HANGE_RULE\x10\n\x12\r\n\tLOG_LEVEL\x10\x0b\x12\x08\n\x04STOP\x10\x0c\x12\x13\n\x0fMONITOR_PROCESS\x10\r\x12\x18\n\x14STOP_MONITOR_PROCESS\x10\x0e**\n\x15NotificationReplyCode\x12\x06\n\x02OK\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x32\xaf\x02\n\x02UI\x12\x34\n\x04Ping\x12\x15.protocol.PingRequest\x1a\x13.protocol.PingReply\"\x00\x12\x31\n\x07\x41skRule\x12\x14.protocol.Connection\x1a\x0e.protocol.Rule\"\x00\x12=\n\tSubscribe\x12\x16.protocol.ClientConfig\x1a\x16.protocol.ClientConfig\"\x00\x12J\n\rNotifications\x12\x1b.protocol.NotificationReply\x1a\x16.protocol.Notification\"\x00(\x01\x30\x01\x12\x35\n\tPostAlert\x12\x0f.protocol.Alert\x1a\x15.protocol.MsgResponse\"\x00\x42\x35Z3github.com/evilsocket/opensnitch/daemon/ui/protocolb\x06proto3')
+)
+
+_ACTION = _descriptor.EnumDescriptor(
+  name='Action',
+  full_name='protocol.Action',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NONE', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ENABLE_INTERCEPTION', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DISABLE_INTERCEPTION', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ENABLE_FIREWALL', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DISABLE_FIREWALL', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RELOAD_FW_RULES', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CHANGE_CONFIG', index=6, number=6,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ENABLE_RULE', index=7, number=7,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DISABLE_RULE', index=8, number=8,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='DELETE_RULE', index=9, number=9,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CHANGE_RULE', index=10, number=10,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='LOG_LEVEL', index=11, number=11,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='STOP', index=12, number=12,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MONITOR_PROCESS', index=13, number=13,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='STOP_MONITOR_PROCESS', index=14, number=14,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=3795,
+  serialized_end=4088,
+)
+_sym_db.RegisterEnumDescriptor(_ACTION)
+
+Action = enum_type_wrapper.EnumTypeWrapper(_ACTION)
+_NOTIFICATIONREPLYCODE = _descriptor.EnumDescriptor(
+  name='NotificationReplyCode',
+  full_name='protocol.NotificationReplyCode',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='OK', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='ERROR', index=1, number=1,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=4090,
+  serialized_end=4132,
+)
+_sym_db.RegisterEnumDescriptor(_NOTIFICATIONREPLYCODE)
+
+NotificationReplyCode = enum_type_wrapper.EnumTypeWrapper(_NOTIFICATIONREPLYCODE)
+NONE = 0
+ENABLE_INTERCEPTION = 1
+DISABLE_INTERCEPTION = 2
+ENABLE_FIREWALL = 3
+DISABLE_FIREWALL = 4
+RELOAD_FW_RULES = 5
+CHANGE_CONFIG = 6
+ENABLE_RULE = 7
+DISABLE_RULE = 8
+DELETE_RULE = 9
+CHANGE_RULE = 10
+LOG_LEVEL = 11
+STOP = 12
+MONITOR_PROCESS = 13
+STOP_MONITOR_PROCESS = 14
+OK = 0
+ERROR = 1
+
+
+_ALERT_PRIORITY = _descriptor.EnumDescriptor(
+  name='Priority',
+  full_name='protocol.Alert.Priority',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='LOW', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='MEDIUM', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='HIGH', index=2, number=2,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=357,
+  serialized_end=398,
+)
+_sym_db.RegisterEnumDescriptor(_ALERT_PRIORITY)
+
+_ALERT_TYPE = _descriptor.EnumDescriptor(
+  name='Type',
+  full_name='protocol.Alert.Type',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='ERROR', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='WARNING', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='INFO', index=2, number=2,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=400,
+  serialized_end=440,
+)
+_sym_db.RegisterEnumDescriptor(_ALERT_TYPE)
+
+_ALERT_ACTION = _descriptor.EnumDescriptor(
+  name='Action',
+  full_name='protocol.Alert.Action',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='NONE', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SHOW_ALERT', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='SAVE_TO_DB', index=2, number=2,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=442,
+  serialized_end=492,
+)
+_sym_db.RegisterEnumDescriptor(_ALERT_ACTION)
+
+_ALERT_WHAT = _descriptor.EnumDescriptor(
+  name='What',
+  full_name='protocol.Alert.What',
+  filename=None,
+  file=DESCRIPTOR,
+  values=[
+    _descriptor.EnumValueDescriptor(
+      name='GENERIC', index=0, number=0,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='PROC_MONITOR', index=1, number=1,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='FIREWALL', index=2, number=2,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='CONNECTION', index=3, number=3,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='RULE', index=4, number=4,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='NETLINK', index=5, number=5,
+      serialized_options=None,
+      type=None),
+    _descriptor.EnumValueDescriptor(
+      name='KERNEL_EVENT', index=6, number=6,
+      serialized_options=None,
+      type=None),
+  ],
+  containing_type=None,
+  serialized_options=None,
+  serialized_start=494,
+  serialized_end=602,
+)
+_sym_db.RegisterEnumDescriptor(_ALERT_WHAT)
+
+
+_ALERT = _descriptor.Descriptor(
+  name='Alert',
+  full_name='protocol.Alert',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.Alert.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='protocol.Alert.type', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='action', full_name='protocol.Alert.action', index=2,
+      number=3, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='priority', full_name='protocol.Alert.priority', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='what', full_name='protocol.Alert.what', index=4,
+      number=5, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='text', full_name='protocol.Alert.text', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='proc', full_name='protocol.Alert.proc', index=6,
+      number=8, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='conn', full_name='protocol.Alert.conn', index=7,
+      number=9, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rule', full_name='protocol.Alert.rule', index=8,
+      number=10, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='fwrule', full_name='protocol.Alert.fwrule', index=9,
+      number=11, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+    _ALERT_PRIORITY,
+    _ALERT_TYPE,
+    _ALERT_ACTION,
+    _ALERT_WHAT,
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+    _descriptor.OneofDescriptor(
+      name='data', full_name='protocol.Alert.data',
+      index=0, containing_type=None, fields=[]),
+  ],
+  serialized_start=23,
+  serialized_end=610,
+)
+
+
+_MSGRESPONSE = _descriptor.Descriptor(
+  name='MsgResponse',
+  full_name='protocol.MsgResponse',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.MsgResponse.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=612,
+  serialized_end=637,
+)
+
+
+_EVENT = _descriptor.Descriptor(
+  name='Event',
+  full_name='protocol.Event',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='time', full_name='protocol.Event.time', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='connection', full_name='protocol.Event.connection', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rule', full_name='protocol.Event.rule', index=2,
+      number=3, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='unixnano', full_name='protocol.Event.unixnano', index=3,
+      number=4, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=639,
+  serialized_end=750,
+)
+
+
+_STATISTICS_BYPROTOENTRY = _descriptor.Descriptor(
+  name='ByProtoEntry',
+  full_name='protocol.Statistics.ByProtoEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Statistics.ByProtoEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Statistics.ByProtoEntry.value', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1315,
+  serialized_end=1361,
+)
+
+_STATISTICS_BYADDRESSENTRY = _descriptor.Descriptor(
+  name='ByAddressEntry',
+  full_name='protocol.Statistics.ByAddressEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Statistics.ByAddressEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Statistics.ByAddressEntry.value', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1363,
+  serialized_end=1411,
+)
+
+_STATISTICS_BYHOSTENTRY = _descriptor.Descriptor(
+  name='ByHostEntry',
+  full_name='protocol.Statistics.ByHostEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Statistics.ByHostEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Statistics.ByHostEntry.value', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1413,
+  serialized_end=1458,
+)
+
+_STATISTICS_BYPORTENTRY = _descriptor.Descriptor(
+  name='ByPortEntry',
+  full_name='protocol.Statistics.ByPortEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Statistics.ByPortEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Statistics.ByPortEntry.value', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1460,
+  serialized_end=1505,
+)
+
+_STATISTICS_BYUIDENTRY = _descriptor.Descriptor(
+  name='ByUidEntry',
+  full_name='protocol.Statistics.ByUidEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Statistics.ByUidEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Statistics.ByUidEntry.value', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1507,
+  serialized_end=1551,
+)
+
+_STATISTICS_BYEXECUTABLEENTRY = _descriptor.Descriptor(
+  name='ByExecutableEntry',
+  full_name='protocol.Statistics.ByExecutableEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Statistics.ByExecutableEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Statistics.ByExecutableEntry.value', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1553,
+  serialized_end=1604,
+)
+
+_STATISTICS = _descriptor.Descriptor(
+  name='Statistics',
+  full_name='protocol.Statistics',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='daemon_version', full_name='protocol.Statistics.daemon_version', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rules', full_name='protocol.Statistics.rules', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='uptime', full_name='protocol.Statistics.uptime', index=2,
+      number=3, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dns_responses', full_name='protocol.Statistics.dns_responses', index=3,
+      number=4, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='connections', full_name='protocol.Statistics.connections', index=4,
+      number=5, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ignored', full_name='protocol.Statistics.ignored', index=5,
+      number=6, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='accepted', full_name='protocol.Statistics.accepted', index=6,
+      number=7, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dropped', full_name='protocol.Statistics.dropped', index=7,
+      number=8, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rule_hits', full_name='protocol.Statistics.rule_hits', index=8,
+      number=9, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rule_misses', full_name='protocol.Statistics.rule_misses', index=9,
+      number=10, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='by_proto', full_name='protocol.Statistics.by_proto', index=10,
+      number=11, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='by_address', full_name='protocol.Statistics.by_address', index=11,
+      number=12, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='by_host', full_name='protocol.Statistics.by_host', index=12,
+      number=13, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='by_port', full_name='protocol.Statistics.by_port', index=13,
+      number=14, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='by_uid', full_name='protocol.Statistics.by_uid', index=14,
+      number=15, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='by_executable', full_name='protocol.Statistics.by_executable', index=15,
+      number=16, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='events', full_name='protocol.Statistics.events', index=16,
+      number=17, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_STATISTICS_BYPROTOENTRY, _STATISTICS_BYADDRESSENTRY, _STATISTICS_BYHOSTENTRY, _STATISTICS_BYPORTENTRY, _STATISTICS_BYUIDENTRY, _STATISTICS_BYEXECUTABLEENTRY, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=753,
+  serialized_end=1604,
+)
+
+
+_PINGREQUEST = _descriptor.Descriptor(
+  name='PingRequest',
+  full_name='protocol.PingRequest',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.PingRequest.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='stats', full_name='protocol.PingRequest.stats', index=1,
+      number=2, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1606,
+  serialized_end=1668,
+)
+
+
+_PINGREPLY = _descriptor.Descriptor(
+  name='PingReply',
+  full_name='protocol.PingReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.PingReply.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1670,
+  serialized_end=1693,
+)
+
+
+_PROCESS_ENVENTRY = _descriptor.Descriptor(
+  name='EnvEntry',
+  full_name='protocol.Process.EnvEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Process.EnvEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Process.EnvEntry.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1919,
+  serialized_end=1961,
+)
+
+_PROCESS = _descriptor.Descriptor(
+  name='Process',
+  full_name='protocol.Process',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='pid', full_name='protocol.Process.pid', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='ppid', full_name='protocol.Process.ppid', index=1,
+      number=2, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='uid', full_name='protocol.Process.uid', index=2,
+      number=3, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='comm', full_name='protocol.Process.comm', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='path', full_name='protocol.Process.path', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='args', full_name='protocol.Process.args', index=5,
+      number=6, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='env', full_name='protocol.Process.env', index=6,
+      number=7, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='cwd', full_name='protocol.Process.cwd', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='io_reads', full_name='protocol.Process.io_reads', index=8,
+      number=9, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='io_writes', full_name='protocol.Process.io_writes', index=9,
+      number=10, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='net_reads', full_name='protocol.Process.net_reads', index=10,
+      number=11, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='net_writes', full_name='protocol.Process.net_writes', index=11,
+      number=12, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_PROCESS_ENVENTRY, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1696,
+  serialized_end=1961,
+)
+
+
+_CONNECTION_PROCESSENVENTRY = _descriptor.Descriptor(
+  name='ProcessEnvEntry',
+  full_name='protocol.Connection.ProcessEnvEntry',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='key', full_name='protocol.Connection.ProcessEnvEntry.key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='value', full_name='protocol.Connection.ProcessEnvEntry.value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=_b('8\001'),
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2243,
+  serialized_end=2292,
+)
+
+_CONNECTION = _descriptor.Descriptor(
+  name='Connection',
+  full_name='protocol.Connection',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='protocol', full_name='protocol.Connection.protocol', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='src_ip', full_name='protocol.Connection.src_ip', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='src_port', full_name='protocol.Connection.src_port', index=2,
+      number=3, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dst_ip', full_name='protocol.Connection.dst_ip', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dst_host', full_name='protocol.Connection.dst_host', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='dst_port', full_name='protocol.Connection.dst_port', index=5,
+      number=6, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='user_id', full_name='protocol.Connection.user_id', index=6,
+      number=7, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='process_id', full_name='protocol.Connection.process_id', index=7,
+      number=8, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='process_path', full_name='protocol.Connection.process_path', index=8,
+      number=9, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='process_cwd', full_name='protocol.Connection.process_cwd', index=9,
+      number=10, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='process_args', full_name='protocol.Connection.process_args', index=10,
+      number=11, type=9, cpp_type=9, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='process_env', full_name='protocol.Connection.process_env', index=11,
+      number=12, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[_CONNECTION_PROCESSENVENTRY, ],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=1964,
+  serialized_end=2292,
+)
+
+
+_OPERATOR = _descriptor.Descriptor(
+  name='Operator',
+  full_name='protocol.Operator',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='type', full_name='protocol.Operator.type', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='operand', full_name='protocol.Operator.operand', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='data', full_name='protocol.Operator.data', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='sensitive', full_name='protocol.Operator.sensitive', index=3,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='list', full_name='protocol.Operator.list', index=4,
+      number=5, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2294,
+  serialized_end=2402,
+)
+
+
+_RULE = _descriptor.Descriptor(
+  name='Rule',
+  full_name='protocol.Rule',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='created', full_name='protocol.Rule.created', index=0,
+      number=1, type=3, cpp_type=2, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='name', full_name='protocol.Rule.name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='description', full_name='protocol.Rule.description', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='enabled', full_name='protocol.Rule.enabled', index=3,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='precedence', full_name='protocol.Rule.precedence', index=4,
+      number=5, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='nolog', full_name='protocol.Rule.nolog', index=5,
+      number=6, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='action', full_name='protocol.Rule.action', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='duration', full_name='protocol.Rule.duration', index=7,
+      number=8, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='operator', full_name='protocol.Rule.operator', index=8,
+      number=9, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2405,
+  serialized_end=2587,
+)
+
+
+_STATEMENTVALUES = _descriptor.Descriptor(
+  name='StatementValues',
+  full_name='protocol.StatementValues',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Key', full_name='protocol.StatementValues.Key', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Value', full_name='protocol.StatementValues.Value', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2589,
+  serialized_end=2634,
+)
+
+
+_STATEMENT = _descriptor.Descriptor(
+  name='Statement',
+  full_name='protocol.Statement',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Op', full_name='protocol.Statement.Op', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Name', full_name='protocol.Statement.Name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Values', full_name='protocol.Statement.Values', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2636,
+  serialized_end=2716,
+)
+
+
+_EXPRESSIONS = _descriptor.Descriptor(
+  name='Expressions',
+  full_name='protocol.Expressions',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Statement', full_name='protocol.Expressions.Statement', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2718,
+  serialized_end=2771,
+)
+
+
+_FWRULE = _descriptor.Descriptor(
+  name='FwRule',
+  full_name='protocol.FwRule',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Table', full_name='protocol.FwRule.Table', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Chain', full_name='protocol.FwRule.Chain', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='UUID', full_name='protocol.FwRule.UUID', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Enabled', full_name='protocol.FwRule.Enabled', index=3,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Position', full_name='protocol.FwRule.Position', index=4,
+      number=5, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Description', full_name='protocol.FwRule.Description', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Parameters', full_name='protocol.FwRule.Parameters', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Expressions', full_name='protocol.FwRule.Expressions', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Target', full_name='protocol.FwRule.Target', index=8,
+      number=9, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='TargetParameters', full_name='protocol.FwRule.TargetParameters', index=9,
+      number=10, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2774,
+  serialized_end=2988,
+)
+
+
+_FWCHAIN = _descriptor.Descriptor(
+  name='FwChain',
+  full_name='protocol.FwChain',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Name', full_name='protocol.FwChain.Name', index=0,
+      number=1, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Table', full_name='protocol.FwChain.Table', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Family', full_name='protocol.FwChain.Family', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Priority', full_name='protocol.FwChain.Priority', index=3,
+      number=4, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Type', full_name='protocol.FwChain.Type', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Hook', full_name='protocol.FwChain.Hook', index=5,
+      number=6, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Policy', full_name='protocol.FwChain.Policy', index=6,
+      number=7, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Rules', full_name='protocol.FwChain.Rules', index=7,
+      number=8, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=2991,
+  serialized_end=3140,
+)
+
+
+_FWCHAINS = _descriptor.Descriptor(
+  name='FwChains',
+  full_name='protocol.FwChains',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Rule', full_name='protocol.FwChains.Rule', index=0,
+      number=1, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Chains', full_name='protocol.FwChains.Chains', index=1,
+      number=2, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3142,
+  serialized_end=3219,
+)
+
+
+_SYSFIREWALL = _descriptor.Descriptor(
+  name='SysFirewall',
+  full_name='protocol.SysFirewall',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='Enabled', full_name='protocol.SysFirewall.Enabled', index=0,
+      number=1, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='Version', full_name='protocol.SysFirewall.Version', index=1,
+      number=2, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='SystemRules', full_name='protocol.SysFirewall.SystemRules', index=2,
+      number=3, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3221,
+  serialized_end=3309,
+)
+
+
+_CLIENTCONFIG = _descriptor.Descriptor(
+  name='ClientConfig',
+  full_name='protocol.ClientConfig',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.ClientConfig.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='name', full_name='protocol.ClientConfig.name', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='version', full_name='protocol.ClientConfig.version', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='isFirewallRunning', full_name='protocol.ClientConfig.isFirewallRunning', index=3,
+      number=4, type=8, cpp_type=7, label=1,
+      has_default_value=False, default_value=False,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='config', full_name='protocol.ClientConfig.config', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='logLevel', full_name='protocol.ClientConfig.logLevel', index=5,
+      number=6, type=13, cpp_type=3, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rules', full_name='protocol.ClientConfig.rules', index=6,
+      number=7, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='systemFirewall', full_name='protocol.ClientConfig.systemFirewall', index=7,
+      number=8, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3312,
+  serialized_end=3508,
+)
+
+
+_NOTIFICATION = _descriptor.Descriptor(
+  name='Notification',
+  full_name='protocol.Notification',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.Notification.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='clientName', full_name='protocol.Notification.clientName', index=1,
+      number=2, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='serverName', full_name='protocol.Notification.serverName', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='type', full_name='protocol.Notification.type', index=3,
+      number=4, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='data', full_name='protocol.Notification.data', index=4,
+      number=5, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='rules', full_name='protocol.Notification.rules', index=5,
+      number=6, type=11, cpp_type=10, label=3,
+      has_default_value=False, default_value=[],
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='sysFirewall', full_name='protocol.Notification.sysFirewall', index=6,
+      number=7, type=11, cpp_type=10, label=1,
+      has_default_value=False, default_value=None,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3511,
+  serialized_end=3698,
+)
+
+
+_NOTIFICATIONREPLY = _descriptor.Descriptor(
+  name='NotificationReply',
+  full_name='protocol.NotificationReply',
+  filename=None,
+  file=DESCRIPTOR,
+  containing_type=None,
+  fields=[
+    _descriptor.FieldDescriptor(
+      name='id', full_name='protocol.NotificationReply.id', index=0,
+      number=1, type=4, cpp_type=4, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='code', full_name='protocol.NotificationReply.code', index=1,
+      number=2, type=14, cpp_type=8, label=1,
+      has_default_value=False, default_value=0,
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+    _descriptor.FieldDescriptor(
+      name='data', full_name='protocol.NotificationReply.data', index=2,
+      number=3, type=9, cpp_type=9, label=1,
+      has_default_value=False, default_value=_b("").decode('utf-8'),
+      message_type=None, enum_type=None, containing_type=None,
+      is_extension=False, extension_scope=None,
+      serialized_options=None, file=DESCRIPTOR),
+  ],
+  extensions=[
+  ],
+  nested_types=[],
+  enum_types=[
+  ],
+  serialized_options=None,
+  is_extendable=False,
+  syntax='proto3',
+  extension_ranges=[],
+  oneofs=[
+  ],
+  serialized_start=3700,
+  serialized_end=3792,
+)
+
+_ALERT.fields_by_name['type'].enum_type = _ALERT_TYPE
+_ALERT.fields_by_name['action'].enum_type = _ALERT_ACTION
+_ALERT.fields_by_name['priority'].enum_type = _ALERT_PRIORITY
+_ALERT.fields_by_name['what'].enum_type = _ALERT_WHAT
+_ALERT.fields_by_name['proc'].message_type = _PROCESS
+_ALERT.fields_by_name['conn'].message_type = _CONNECTION
+_ALERT.fields_by_name['rule'].message_type = _RULE
+_ALERT.fields_by_name['fwrule'].message_type = _FWRULE
+_ALERT_PRIORITY.containing_type = _ALERT
+_ALERT_TYPE.containing_type = _ALERT
+_ALERT_ACTION.containing_type = _ALERT
+_ALERT_WHAT.containing_type = _ALERT
+_ALERT.oneofs_by_name['data'].fields.append(
+  _ALERT.fields_by_name['text'])
+_ALERT.fields_by_name['text'].containing_oneof = _ALERT.oneofs_by_name['data']
+_ALERT.oneofs_by_name['data'].fields.append(
+  _ALERT.fields_by_name['proc'])
+_ALERT.fields_by_name['proc'].containing_oneof = _ALERT.oneofs_by_name['data']
+_ALERT.oneofs_by_name['data'].fields.append(
+  _ALERT.fields_by_name['conn'])
+_ALERT.fields_by_name['conn'].containing_oneof = _ALERT.oneofs_by_name['data']
+_ALERT.oneofs_by_name['data'].fields.append(
+  _ALERT.fields_by_name['rule'])
+_ALERT.fields_by_name['rule'].containing_oneof = _ALERT.oneofs_by_name['data']
+_ALERT.oneofs_by_name['data'].fields.append(
+  _ALERT.fields_by_name['fwrule'])
+_ALERT.fields_by_name['fwrule'].containing_oneof = _ALERT.oneofs_by_name['data']
+_EVENT.fields_by_name['connection'].message_type = _CONNECTION
+_EVENT.fields_by_name['rule'].message_type = _RULE
+_STATISTICS_BYPROTOENTRY.containing_type = _STATISTICS
+_STATISTICS_BYADDRESSENTRY.containing_type = _STATISTICS
+_STATISTICS_BYHOSTENTRY.containing_type = _STATISTICS
+_STATISTICS_BYPORTENTRY.containing_type = _STATISTICS
+_STATISTICS_BYUIDENTRY.containing_type = _STATISTICS
+_STATISTICS_BYEXECUTABLEENTRY.containing_type = _STATISTICS
+_STATISTICS.fields_by_name['by_proto'].message_type = _STATISTICS_BYPROTOENTRY
+_STATISTICS.fields_by_name['by_address'].message_type = _STATISTICS_BYADDRESSENTRY
+_STATISTICS.fields_by_name['by_host'].message_type = _STATISTICS_BYHOSTENTRY
+_STATISTICS.fields_by_name['by_port'].message_type = _STATISTICS_BYPORTENTRY
+_STATISTICS.fields_by_name['by_uid'].message_type = _STATISTICS_BYUIDENTRY
+_STATISTICS.fields_by_name['by_executable'].message_type = _STATISTICS_BYEXECUTABLEENTRY
+_STATISTICS.fields_by_name['events'].message_type = _EVENT
+_PINGREQUEST.fields_by_name['stats'].message_type = _STATISTICS
+_PROCESS_ENVENTRY.containing_type = _PROCESS
+_PROCESS.fields_by_name['env'].message_type = _PROCESS_ENVENTRY
+_CONNECTION_PROCESSENVENTRY.containing_type = _CONNECTION
+_CONNECTION.fields_by_name['process_env'].message_type = _CONNECTION_PROCESSENVENTRY
+_OPERATOR.fields_by_name['list'].message_type = _OPERATOR
+_RULE.fields_by_name['operator'].message_type = _OPERATOR
+_STATEMENT.fields_by_name['Values'].message_type = _STATEMENTVALUES
+_EXPRESSIONS.fields_by_name['Statement'].message_type = _STATEMENT
+_FWRULE.fields_by_name['Expressions'].message_type = _EXPRESSIONS
+_FWCHAIN.fields_by_name['Rules'].message_type = _FWRULE
+_FWCHAINS.fields_by_name['Rule'].message_type = _FWRULE
+_FWCHAINS.fields_by_name['Chains'].message_type = _FWCHAIN
+_SYSFIREWALL.fields_by_name['SystemRules'].message_type = _FWCHAINS
+_CLIENTCONFIG.fields_by_name['rules'].message_type = _RULE
+_CLIENTCONFIG.fields_by_name['systemFirewall'].message_type = _SYSFIREWALL
+_NOTIFICATION.fields_by_name['type'].enum_type = _ACTION
+_NOTIFICATION.fields_by_name['rules'].message_type = _RULE
+_NOTIFICATION.fields_by_name['sysFirewall'].message_type = _SYSFIREWALL
+_NOTIFICATIONREPLY.fields_by_name['code'].enum_type = _NOTIFICATIONREPLYCODE
+DESCRIPTOR.message_types_by_name['Alert'] = _ALERT
+DESCRIPTOR.message_types_by_name['MsgResponse'] = _MSGRESPONSE
+DESCRIPTOR.message_types_by_name['Event'] = _EVENT
+DESCRIPTOR.message_types_by_name['Statistics'] = _STATISTICS
+DESCRIPTOR.message_types_by_name['PingRequest'] = _PINGREQUEST
+DESCRIPTOR.message_types_by_name['PingReply'] = _PINGREPLY
+DESCRIPTOR.message_types_by_name['Process'] = _PROCESS
+DESCRIPTOR.message_types_by_name['Connection'] = _CONNECTION
+DESCRIPTOR.message_types_by_name['Operator'] = _OPERATOR
+DESCRIPTOR.message_types_by_name['Rule'] = _RULE
+DESCRIPTOR.message_types_by_name['StatementValues'] = _STATEMENTVALUES
+DESCRIPTOR.message_types_by_name['Statement'] = _STATEMENT
+DESCRIPTOR.message_types_by_name['Expressions'] = _EXPRESSIONS
+DESCRIPTOR.message_types_by_name['FwRule'] = _FWRULE
+DESCRIPTOR.message_types_by_name['FwChain'] = _FWCHAIN
+DESCRIPTOR.message_types_by_name['FwChains'] = _FWCHAINS
+DESCRIPTOR.message_types_by_name['SysFirewall'] = _SYSFIREWALL
+DESCRIPTOR.message_types_by_name['ClientConfig'] = _CLIENTCONFIG
+DESCRIPTOR.message_types_by_name['Notification'] = _NOTIFICATION
+DESCRIPTOR.message_types_by_name['NotificationReply'] = _NOTIFICATIONREPLY
+DESCRIPTOR.enum_types_by_name['Action'] = _ACTION
+DESCRIPTOR.enum_types_by_name['NotificationReplyCode'] = _NOTIFICATIONREPLYCODE
+_sym_db.RegisterFileDescriptor(DESCRIPTOR)
+
+Alert = _reflection.GeneratedProtocolMessageType('Alert', (_message.Message,), {
+  'DESCRIPTOR' : _ALERT,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Alert)
+  })
+_sym_db.RegisterMessage(Alert)
+
+MsgResponse = _reflection.GeneratedProtocolMessageType('MsgResponse', (_message.Message,), {
+  'DESCRIPTOR' : _MSGRESPONSE,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.MsgResponse)
+  })
+_sym_db.RegisterMessage(MsgResponse)
+
+Event = _reflection.GeneratedProtocolMessageType('Event', (_message.Message,), {
+  'DESCRIPTOR' : _EVENT,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Event)
+  })
+_sym_db.RegisterMessage(Event)
+
+Statistics = _reflection.GeneratedProtocolMessageType('Statistics', (_message.Message,), {
+
+  'ByProtoEntry' : _reflection.GeneratedProtocolMessageType('ByProtoEntry', (_message.Message,), {
+    'DESCRIPTOR' : _STATISTICS_BYPROTOENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Statistics.ByProtoEntry)
+    })
+  ,
+
+  'ByAddressEntry' : _reflection.GeneratedProtocolMessageType('ByAddressEntry', (_message.Message,), {
+    'DESCRIPTOR' : _STATISTICS_BYADDRESSENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Statistics.ByAddressEntry)
+    })
+  ,
+
+  'ByHostEntry' : _reflection.GeneratedProtocolMessageType('ByHostEntry', (_message.Message,), {
+    'DESCRIPTOR' : _STATISTICS_BYHOSTENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Statistics.ByHostEntry)
+    })
+  ,
+
+  'ByPortEntry' : _reflection.GeneratedProtocolMessageType('ByPortEntry', (_message.Message,), {
+    'DESCRIPTOR' : _STATISTICS_BYPORTENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Statistics.ByPortEntry)
+    })
+  ,
+
+  'ByUidEntry' : _reflection.GeneratedProtocolMessageType('ByUidEntry', (_message.Message,), {
+    'DESCRIPTOR' : _STATISTICS_BYUIDENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Statistics.ByUidEntry)
+    })
+  ,
+
+  'ByExecutableEntry' : _reflection.GeneratedProtocolMessageType('ByExecutableEntry', (_message.Message,), {
+    'DESCRIPTOR' : _STATISTICS_BYEXECUTABLEENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Statistics.ByExecutableEntry)
+    })
+  ,
+  'DESCRIPTOR' : _STATISTICS,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Statistics)
+  })
+_sym_db.RegisterMessage(Statistics)
+_sym_db.RegisterMessage(Statistics.ByProtoEntry)
+_sym_db.RegisterMessage(Statistics.ByAddressEntry)
+_sym_db.RegisterMessage(Statistics.ByHostEntry)
+_sym_db.RegisterMessage(Statistics.ByPortEntry)
+_sym_db.RegisterMessage(Statistics.ByUidEntry)
+_sym_db.RegisterMessage(Statistics.ByExecutableEntry)
+
+PingRequest = _reflection.GeneratedProtocolMessageType('PingRequest', (_message.Message,), {
+  'DESCRIPTOR' : _PINGREQUEST,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.PingRequest)
+  })
+_sym_db.RegisterMessage(PingRequest)
+
+PingReply = _reflection.GeneratedProtocolMessageType('PingReply', (_message.Message,), {
+  'DESCRIPTOR' : _PINGREPLY,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.PingReply)
+  })
+_sym_db.RegisterMessage(PingReply)
+
+Process = _reflection.GeneratedProtocolMessageType('Process', (_message.Message,), {
+
+  'EnvEntry' : _reflection.GeneratedProtocolMessageType('EnvEntry', (_message.Message,), {
+    'DESCRIPTOR' : _PROCESS_ENVENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Process.EnvEntry)
+    })
+  ,
+  'DESCRIPTOR' : _PROCESS,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Process)
+  })
+_sym_db.RegisterMessage(Process)
+_sym_db.RegisterMessage(Process.EnvEntry)
+
+Connection = _reflection.GeneratedProtocolMessageType('Connection', (_message.Message,), {
+
+  'ProcessEnvEntry' : _reflection.GeneratedProtocolMessageType('ProcessEnvEntry', (_message.Message,), {
+    'DESCRIPTOR' : _CONNECTION_PROCESSENVENTRY,
+    '__module__' : 'ui_pb2'
+    # @@protoc_insertion_point(class_scope:protocol.Connection.ProcessEnvEntry)
+    })
+  ,
+  'DESCRIPTOR' : _CONNECTION,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Connection)
+  })
+_sym_db.RegisterMessage(Connection)
+_sym_db.RegisterMessage(Connection.ProcessEnvEntry)
+
+Operator = _reflection.GeneratedProtocolMessageType('Operator', (_message.Message,), {
+  'DESCRIPTOR' : _OPERATOR,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Operator)
+  })
+_sym_db.RegisterMessage(Operator)
+
+Rule = _reflection.GeneratedProtocolMessageType('Rule', (_message.Message,), {
+  'DESCRIPTOR' : _RULE,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Rule)
+  })
+_sym_db.RegisterMessage(Rule)
+
+StatementValues = _reflection.GeneratedProtocolMessageType('StatementValues', (_message.Message,), {
+  'DESCRIPTOR' : _STATEMENTVALUES,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.StatementValues)
+  })
+_sym_db.RegisterMessage(StatementValues)
+
+Statement = _reflection.GeneratedProtocolMessageType('Statement', (_message.Message,), {
+  'DESCRIPTOR' : _STATEMENT,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Statement)
+  })
+_sym_db.RegisterMessage(Statement)
+
+Expressions = _reflection.GeneratedProtocolMessageType('Expressions', (_message.Message,), {
+  'DESCRIPTOR' : _EXPRESSIONS,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Expressions)
+  })
+_sym_db.RegisterMessage(Expressions)
+
+FwRule = _reflection.GeneratedProtocolMessageType('FwRule', (_message.Message,), {
+  'DESCRIPTOR' : _FWRULE,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.FwRule)
+  })
+_sym_db.RegisterMessage(FwRule)
+
+FwChain = _reflection.GeneratedProtocolMessageType('FwChain', (_message.Message,), {
+  'DESCRIPTOR' : _FWCHAIN,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.FwChain)
+  })
+_sym_db.RegisterMessage(FwChain)
+
+FwChains = _reflection.GeneratedProtocolMessageType('FwChains', (_message.Message,), {
+  'DESCRIPTOR' : _FWCHAINS,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.FwChains)
+  })
+_sym_db.RegisterMessage(FwChains)
+
+SysFirewall = _reflection.GeneratedProtocolMessageType('SysFirewall', (_message.Message,), {
+  'DESCRIPTOR' : _SYSFIREWALL,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.SysFirewall)
+  })
+_sym_db.RegisterMessage(SysFirewall)
+
+ClientConfig = _reflection.GeneratedProtocolMessageType('ClientConfig', (_message.Message,), {
+  'DESCRIPTOR' : _CLIENTCONFIG,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.ClientConfig)
+  })
+_sym_db.RegisterMessage(ClientConfig)
+
+Notification = _reflection.GeneratedProtocolMessageType('Notification', (_message.Message,), {
+  'DESCRIPTOR' : _NOTIFICATION,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.Notification)
+  })
+_sym_db.RegisterMessage(Notification)
+
+NotificationReply = _reflection.GeneratedProtocolMessageType('NotificationReply', (_message.Message,), {
+  'DESCRIPTOR' : _NOTIFICATIONREPLY,
+  '__module__' : 'ui_pb2'
+  # @@protoc_insertion_point(class_scope:protocol.NotificationReply)
+  })
+_sym_db.RegisterMessage(NotificationReply)
+
+
+DESCRIPTOR._options = None
+_STATISTICS_BYPROTOENTRY._options = None
+_STATISTICS_BYADDRESSENTRY._options = None
+_STATISTICS_BYHOSTENTRY._options = None
+_STATISTICS_BYPORTENTRY._options = None
+_STATISTICS_BYUIDENTRY._options = None
+_STATISTICS_BYEXECUTABLEENTRY._options = None
+_PROCESS_ENVENTRY._options = None
+_CONNECTION_PROCESSENVENTRY._options = None
+
+_UI = _descriptor.ServiceDescriptor(
+  name='UI',
+  full_name='protocol.UI',
+  file=DESCRIPTOR,
+  index=0,
+  serialized_options=None,
+  serialized_start=4135,
+  serialized_end=4438,
+  methods=[
+  _descriptor.MethodDescriptor(
+    name='Ping',
+    full_name='protocol.UI.Ping',
+    index=0,
+    containing_service=None,
+    input_type=_PINGREQUEST,
+    output_type=_PINGREPLY,
+    serialized_options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='AskRule',
+    full_name='protocol.UI.AskRule',
+    index=1,
+    containing_service=None,
+    input_type=_CONNECTION,
+    output_type=_RULE,
+    serialized_options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='Subscribe',
+    full_name='protocol.UI.Subscribe',
+    index=2,
+    containing_service=None,
+    input_type=_CLIENTCONFIG,
+    output_type=_CLIENTCONFIG,
+    serialized_options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='Notifications',
+    full_name='protocol.UI.Notifications',
+    index=3,
+    containing_service=None,
+    input_type=_NOTIFICATIONREPLY,
+    output_type=_NOTIFICATION,
+    serialized_options=None,
+  ),
+  _descriptor.MethodDescriptor(
+    name='PostAlert',
+    full_name='protocol.UI.PostAlert',
+    index=4,
+    containing_service=None,
+    input_type=_ALERT,
+    output_type=_MSGRESPONSE,
+    serialized_options=None,
+  ),
+])
+_sym_db.RegisterServiceDescriptor(_UI)
+
+DESCRIPTOR.services_by_name['UI'] = _UI
+
+# @@protoc_insertion_point(module_scope)
diff --git a/ui/opensnitch/proto/pre3200/ui_pb2_grpc.py b/ui/opensnitch/proto/pre3200/ui_pb2_grpc.py
new file mode 100644 (file)
index 0000000..8dd3491
--- /dev/null
@@ -0,0 +1,114 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+import grpc
+
+from . import ui_pb2 as ui__pb2
+
+
+class UIStub(object):
+  # missing associated documentation comment in .proto file
+  pass
+
+  def __init__(self, channel):
+    """Constructor.
+
+    Args:
+      channel: A grpc.Channel.
+    """
+    self.Ping = channel.unary_unary(
+        '/protocol.UI/Ping',
+        request_serializer=ui__pb2.PingRequest.SerializeToString,
+        response_deserializer=ui__pb2.PingReply.FromString,
+        )
+    self.AskRule = channel.unary_unary(
+        '/protocol.UI/AskRule',
+        request_serializer=ui__pb2.Connection.SerializeToString,
+        response_deserializer=ui__pb2.Rule.FromString,
+        )
+    self.Subscribe = channel.unary_unary(
+        '/protocol.UI/Subscribe',
+        request_serializer=ui__pb2.ClientConfig.SerializeToString,
+        response_deserializer=ui__pb2.ClientConfig.FromString,
+        )
+    self.Notifications = channel.stream_stream(
+        '/protocol.UI/Notifications',
+        request_serializer=ui__pb2.NotificationReply.SerializeToString,
+        response_deserializer=ui__pb2.Notification.FromString,
+        )
+    self.PostAlert = channel.unary_unary(
+        '/protocol.UI/PostAlert',
+        request_serializer=ui__pb2.Alert.SerializeToString,
+        response_deserializer=ui__pb2.MsgResponse.FromString,
+        )
+
+
+class UIServicer(object):
+  # missing associated documentation comment in .proto file
+  pass
+
+  def Ping(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def AskRule(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def Subscribe(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def Notifications(self, request_iterator, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+  def PostAlert(self, request, context):
+    # missing associated documentation comment in .proto file
+    pass
+    context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+    context.set_details('Method not implemented!')
+    raise NotImplementedError('Method not implemented!')
+
+
+def add_UIServicer_to_server(servicer, server):
+  rpc_method_handlers = {
+      'Ping': grpc.unary_unary_rpc_method_handler(
+          servicer.Ping,
+          request_deserializer=ui__pb2.PingRequest.FromString,
+          response_serializer=ui__pb2.PingReply.SerializeToString,
+      ),
+      'AskRule': grpc.unary_unary_rpc_method_handler(
+          servicer.AskRule,
+          request_deserializer=ui__pb2.Connection.FromString,
+          response_serializer=ui__pb2.Rule.SerializeToString,
+      ),
+      'Subscribe': grpc.unary_unary_rpc_method_handler(
+          servicer.Subscribe,
+          request_deserializer=ui__pb2.ClientConfig.FromString,
+          response_serializer=ui__pb2.ClientConfig.SerializeToString,
+      ),
+      'Notifications': grpc.stream_stream_rpc_method_handler(
+          servicer.Notifications,
+          request_deserializer=ui__pb2.NotificationReply.FromString,
+          response_serializer=ui__pb2.Notification.SerializeToString,
+      ),
+      'PostAlert': grpc.unary_unary_rpc_method_handler(
+          servicer.PostAlert,
+          request_deserializer=ui__pb2.Alert.FromString,
+          response_serializer=ui__pb2.MsgResponse.SerializeToString,
+      ),
+  }
+  generic_handler = grpc.method_handlers_generic_handler(
+      'protocol.UI', rpc_method_handlers)
+  server.add_generic_rpc_handlers((generic_handler,))
diff --git a/ui/opensnitch/proto/ui_pb2.py b/ui/opensnitch/proto/ui_pb2.py
new file mode 100644 (file)
index 0000000..560b7a9
--- /dev/null
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+# Generated by the protocol buffer compiler.  DO NOT EDIT!
+# source: ui.proto
+"""Generated protocol buffer code."""
+from google.protobuf.internal import builder as _builder
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import descriptor_pool as _descriptor_pool
+from google.protobuf import symbol_database as _symbol_database
+# @@protoc_insertion_point(imports)
+
+_sym_db = _symbol_database.Default()
+
+
+
+
+DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x08ui.proto\x12\x08protocol\"\xcb\x04\n\x05\x41lert\x12\n\n\x02id\x18\x01 \x01(\x04\x12\"\n\x04type\x18\x02 \x01(\x0e\x32\x14.protocol.Alert.Type\x12&\n\x06\x61\x63tion\x18\x03 \x01(\x0e\x32\x16.protocol.Alert.Action\x12*\n\x08priority\x18\x04 \x01(\x0e\x32\x18.protocol.Alert.Priority\x12\"\n\x04what\x18\x05 \x01(\x0e\x32\x14.protocol.Alert.What\x12\x0e\n\x04text\x18\x06 \x01(\tH\x00\x12!\n\x04proc\x18\x08 \x01(\x0b\x32\x11.protocol.ProcessH\x00\x12$\n\x04\x63onn\x18\t \x01(\x0b\x32\x14.protocol.ConnectionH\x00\x12\x1e\n\x04rule\x18\n \x01(\x0b\x32\x0e.protocol.RuleH\x00\x12\"\n\x06\x66wrule\x18\x0b \x01(\x0b\x32\x10.protocol.FwRuleH\x00\")\n\x08Priority\x12\x07\n\x03LOW\x10\x00\x12\n\n\x06MEDIUM\x10\x01\x12\x08\n\x04HIGH\x10\x02\"(\n\x04Type\x12\t\n\x05\x45RROR\x10\x00\x12\x0b\n\x07WARNING\x10\x01\x12\x08\n\x04INFO\x10\x02\"2\n\x06\x41\x63tion\x12\x08\n\x04NONE\x10\x00\x12\x0e\n\nSHOW_ALERT\x10\x01\x12\x0e\n\nSAVE_TO_DB\x10\x02\"l\n\x04What\x12\x0b\n\x07GENERIC\x10\x00\x12\x10\n\x0cPROC_MONITOR\x10\x01\x12\x0c\n\x08\x46IREWALL\x10\x02\x12\x0e\n\nCONNECTION\x10\x03\x12\x08\n\x04RULE\x10\x04\x12\x0b\n\x07NETLINK\x10\x05\x12\x10\n\x0cKERNEL_EVENT\x10\x06\x42\x06\n\x04\x64\x61ta\"\x19\n\x0bMsgResponse\x12\n\n\x02id\x18\x01 \x01(\x04\"o\n\x05\x45vent\x12\x0c\n\x04time\x18\x01 \x01(\t\x12(\n\nconnection\x18\x02 \x01(\x0b\x32\x14.protocol.Connection\x12\x1c\n\x04rule\x18\x03 \x01(\x0b\x32\x0e.protocol.Rule\x12\x10\n\x08unixnano\x18\x04 \x01(\x03\"\xd3\x06\n\nStatistics\x12\x16\n\x0e\x64\x61\x65mon_version\x18\x01 \x01(\t\x12\r\n\x05rules\x18\x02 \x01(\x04\x12\x0e\n\x06uptime\x18\x03 \x01(\x04\x12\x15\n\rdns_responses\x18\x04 \x01(\x04\x12\x13\n\x0b\x63onnections\x18\x05 \x01(\x04\x12\x0f\n\x07ignored\x18\x06 \x01(\x04\x12\x10\n\x08\x61\x63\x63\x65pted\x18\x07 \x01(\x04\x12\x0f\n\x07\x64ropped\x18\x08 \x01(\x04\x12\x11\n\trule_hits\x18\t \x01(\x04\x12\x13\n\x0brule_misses\x18\n \x01(\x04\x12\x33\n\x08\x62y_proto\x18\x0b \x03(\x0b\x32!.protocol.Statistics.ByProtoEntry\x12\x37\n\nby_address\x18\x0c \x03(\x0b\x32#.protocol.Statistics.ByAddressEntry\x12\x31\n\x07\x62y_host\x18\r \x03(\x0b\x32 .protocol.Statistics.ByHostEntry\x12\x31\n\x07\x62y_port\x18\x0e \x03(\x0b\x32 .protocol.Statistics.ByPortEntry\x12/\n\x06\x62y_uid\x18\x0f \x03(\x0b\x32\x1f.protocol.Statistics.ByUidEntry\x12=\n\rby_executable\x18\x10 \x03(\x0b\x32&.protocol.Statistics.ByExecutableEntry\x12\x1f\n\x06\x65vents\x18\x11 \x03(\x0b\x32\x0f.protocol.Event\x1a.\n\x0c\x42yProtoEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a\x30\n\x0e\x42yAddressEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a-\n\x0b\x42yHostEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a-\n\x0b\x42yPortEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a,\n\nByUidEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\x1a\x33\n\x11\x42yExecutableEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\x04:\x02\x38\x01\">\n\x0bPingRequest\x12\n\n\x02id\x18\x01 \x01(\x04\x12#\n\x05stats\x18\x02 \x01(\x0b\x32\x14.protocol.Statistics\"\x17\n\tPingReply\x12\n\n\x02id\x18\x01 \x01(\x04\"\x89\x02\n\x07Process\x12\x0b\n\x03pid\x18\x01 \x01(\x04\x12\x0c\n\x04ppid\x18\x02 \x01(\x04\x12\x0b\n\x03uid\x18\x03 \x01(\x04\x12\x0c\n\x04\x63omm\x18\x04 \x01(\t\x12\x0c\n\x04path\x18\x05 \x01(\t\x12\x0c\n\x04\x61rgs\x18\x06 \x03(\t\x12\'\n\x03\x65nv\x18\x07 \x03(\x0b\x32\x1a.protocol.Process.EnvEntry\x12\x0b\n\x03\x63wd\x18\x08 \x01(\t\x12\x10\n\x08io_reads\x18\t \x01(\x04\x12\x11\n\tio_writes\x18\n \x01(\x04\x12\x11\n\tnet_reads\x18\x0b \x01(\x04\x12\x12\n\nnet_writes\x18\x0c \x01(\x04\x1a*\n\x08\x45nvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"\xc8\x02\n\nConnection\x12\x10\n\x08protocol\x18\x01 \x01(\t\x12\x0e\n\x06src_ip\x18\x02 \x01(\t\x12\x10\n\x08src_port\x18\x03 \x01(\r\x12\x0e\n\x06\x64st_ip\x18\x04 \x01(\t\x12\x10\n\x08\x64st_host\x18\x05 \x01(\t\x12\x10\n\x08\x64st_port\x18\x06 \x01(\r\x12\x0f\n\x07user_id\x18\x07 \x01(\r\x12\x12\n\nprocess_id\x18\x08 \x01(\r\x12\x14\n\x0cprocess_path\x18\t \x01(\t\x12\x13\n\x0bprocess_cwd\x18\n \x01(\t\x12\x14\n\x0cprocess_args\x18\x0b \x03(\t\x12\x39\n\x0bprocess_env\x18\x0c \x03(\x0b\x32$.protocol.Connection.ProcessEnvEntry\x1a\x31\n\x0fProcessEnvEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12\r\n\x05value\x18\x02 \x01(\t:\x02\x38\x01\"l\n\x08Operator\x12\x0c\n\x04type\x18\x01 \x01(\t\x12\x0f\n\x07operand\x18\x02 \x01(\t\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\t\x12\x11\n\tsensitive\x18\x04 \x01(\x08\x12 \n\x04list\x18\x05 \x03(\x0b\x32\x12.protocol.Operator\"\xb6\x01\n\x04Rule\x12\x0f\n\x07\x63reated\x18\x01 \x01(\x03\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x13\n\x0b\x64\x65scription\x18\x03 \x01(\t\x12\x0f\n\x07\x65nabled\x18\x04 \x01(\x08\x12\x12\n\nprecedence\x18\x05 \x01(\x08\x12\r\n\x05nolog\x18\x06 \x01(\x08\x12\x0e\n\x06\x61\x63tion\x18\x07 \x01(\t\x12\x10\n\x08\x64uration\x18\x08 \x01(\t\x12$\n\x08operator\x18\t \x01(\x0b\x32\x12.protocol.Operator\"-\n\x0fStatementValues\x12\x0b\n\x03Key\x18\x01 \x01(\t\x12\r\n\x05Value\x18\x02 \x01(\t\"P\n\tStatement\x12\n\n\x02Op\x18\x01 \x01(\t\x12\x0c\n\x04Name\x18\x02 \x01(\t\x12)\n\x06Values\x18\x03 \x03(\x0b\x32\x19.protocol.StatementValues\"5\n\x0b\x45xpressions\x12&\n\tStatement\x18\x01 \x01(\x0b\x32\x13.protocol.Statement\"\xd6\x01\n\x06\x46wRule\x12\r\n\x05Table\x18\x01 \x01(\t\x12\r\n\x05\x43hain\x18\x02 \x01(\t\x12\x0c\n\x04UUID\x18\x03 \x01(\t\x12\x0f\n\x07\x45nabled\x18\x04 \x01(\x08\x12\x10\n\x08Position\x18\x05 \x01(\x04\x12\x13\n\x0b\x44\x65scription\x18\x06 \x01(\t\x12\x12\n\nParameters\x18\x07 \x01(\t\x12*\n\x0b\x45xpressions\x18\x08 \x03(\x0b\x32\x15.protocol.Expressions\x12\x0e\n\x06Target\x18\t \x01(\t\x12\x18\n\x10TargetParameters\x18\n \x01(\t\"\x95\x01\n\x07\x46wChain\x12\x0c\n\x04Name\x18\x01 \x01(\t\x12\r\n\x05Table\x18\x02 \x01(\t\x12\x0e\n\x06\x46\x61mily\x18\x03 \x01(\t\x12\x10\n\x08Priority\x18\x04 \x01(\t\x12\x0c\n\x04Type\x18\x05 \x01(\t\x12\x0c\n\x04Hook\x18\x06 \x01(\t\x12\x0e\n\x06Policy\x18\x07 \x01(\t\x12\x1f\n\x05Rules\x18\x08 \x03(\x0b\x32\x10.protocol.FwRule\"M\n\x08\x46wChains\x12\x1e\n\x04Rule\x18\x01 \x01(\x0b\x32\x10.protocol.FwRule\x12!\n\x06\x43hains\x18\x02 \x03(\x0b\x32\x11.protocol.FwChain\"X\n\x0bSysFirewall\x12\x0f\n\x07\x45nabled\x18\x01 \x01(\x08\x12\x0f\n\x07Version\x18\x02 \x01(\r\x12\'\n\x0bSystemRules\x18\x03 \x03(\x0b\x32\x12.protocol.FwChains\"\xc4\x01\n\x0c\x43lientConfig\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x0c\n\x04name\x18\x02 \x01(\t\x12\x0f\n\x07version\x18\x03 \x01(\t\x12\x19\n\x11isFirewallRunning\x18\x04 \x01(\x08\x12\x0e\n\x06\x63onfig\x18\x05 \x01(\t\x12\x10\n\x08logLevel\x18\x06 \x01(\r\x12\x1d\n\x05rules\x18\x07 \x03(\x0b\x32\x0e.protocol.Rule\x12-\n\x0esystemFirewall\x18\x08 \x01(\x0b\x32\x15.protocol.SysFirewall\"\xbb\x01\n\x0cNotification\x12\n\n\x02id\x18\x01 \x01(\x04\x12\x12\n\nclientName\x18\x02 \x01(\t\x12\x12\n\nserverName\x18\x03 \x01(\t\x12\x1e\n\x04type\x18\x04 \x01(\x0e\x32\x10.protocol.Action\x12\x0c\n\x04\x64\x61ta\x18\x05 \x01(\t\x12\x1d\n\x05rules\x18\x06 \x03(\x0b\x32\x0e.protocol.Rule\x12*\n\x0bsysFirewall\x18\x07 \x01(\x0b\x32\x15.protocol.SysFirewall\"\\\n\x11NotificationReply\x12\n\n\x02id\x18\x01 \x01(\x04\x12-\n\x04\x63ode\x18\x02 \x01(\x0e\x32\x1f.protocol.NotificationReplyCode\x12\x0c\n\x04\x64\x61ta\x18\x03 \x01(\t*\xa5\x02\n\x06\x41\x63tion\x12\x08\n\x04NONE\x10\x00\x12\x17\n\x13\x45NABLE_INTERCEPTION\x10\x01\x12\x18\n\x14\x44ISABLE_INTERCEPTION\x10\x02\x12\x13\n\x0f\x45NABLE_FIREWALL\x10\x03\x12\x14\n\x10\x44ISABLE_FIREWALL\x10\x04\x12\x13\n\x0fRELOAD_FW_RULES\x10\x05\x12\x11\n\rCHANGE_CONFIG\x10\x06\x12\x0f\n\x0b\x45NABLE_RULE\x10\x07\x12\x10\n\x0c\x44ISABLE_RULE\x10\x08\x12\x0f\n\x0b\x44\x45LETE_RULE\x10\t\x12\x0f\n\x0b\x43HANGE_RULE\x10\n\x12\r\n\tLOG_LEVEL\x10\x0b\x12\x08\n\x04STOP\x10\x0c\x12\x13\n\x0fMONITOR_PROCESS\x10\r\x12\x18\n\x14STOP_MONITOR_PROCESS\x10\x0e**\n\x15NotificationReplyCode\x12\x06\n\x02OK\x10\x00\x12\t\n\x05\x45RROR\x10\x01\x32\xaf\x02\n\x02UI\x12\x34\n\x04Ping\x12\x15.protocol.PingRequest\x1a\x13.protocol.PingReply\"\x00\x12\x31\n\x07\x41skRule\x12\x14.protocol.Connection\x1a\x0e.protocol.Rule\"\x00\x12=\n\tSubscribe\x12\x16.protocol.ClientConfig\x1a\x16.protocol.ClientConfig\"\x00\x12J\n\rNotifications\x12\x1b.protocol.NotificationReply\x1a\x16.protocol.Notification\"\x00(\x01\x30\x01\x12\x35\n\tPostAlert\x12\x0f.protocol.Alert\x1a\x15.protocol.MsgResponse\"\x00\x42\x35Z3github.com/evilsocket/opensnitch/daemon/ui/protocolb\x06proto3')
+
+_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, globals())
+_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'ui_pb2', globals())
+if _descriptor._USE_C_DESCRIPTORS == False:
+
+  DESCRIPTOR._options = None
+  DESCRIPTOR._serialized_options = b'Z3github.com/evilsocket/opensnitch/daemon/ui/protocol'
+  _STATISTICS_BYPROTOENTRY._options = None
+  _STATISTICS_BYPROTOENTRY._serialized_options = b'8\001'
+  _STATISTICS_BYADDRESSENTRY._options = None
+  _STATISTICS_BYADDRESSENTRY._serialized_options = b'8\001'
+  _STATISTICS_BYHOSTENTRY._options = None
+  _STATISTICS_BYHOSTENTRY._serialized_options = b'8\001'
+  _STATISTICS_BYPORTENTRY._options = None
+  _STATISTICS_BYPORTENTRY._serialized_options = b'8\001'
+  _STATISTICS_BYUIDENTRY._options = None
+  _STATISTICS_BYUIDENTRY._serialized_options = b'8\001'
+  _STATISTICS_BYEXECUTABLEENTRY._options = None
+  _STATISTICS_BYEXECUTABLEENTRY._serialized_options = b'8\001'
+  _PROCESS_ENVENTRY._options = None
+  _PROCESS_ENVENTRY._serialized_options = b'8\001'
+  _CONNECTION_PROCESSENVENTRY._options = None
+  _CONNECTION_PROCESSENVENTRY._serialized_options = b'8\001'
+  _ACTION._serialized_start=3795
+  _ACTION._serialized_end=4088
+  _NOTIFICATIONREPLYCODE._serialized_start=4090
+  _NOTIFICATIONREPLYCODE._serialized_end=4132
+  _ALERT._serialized_start=23
+  _ALERT._serialized_end=610
+  _ALERT_PRIORITY._serialized_start=357
+  _ALERT_PRIORITY._serialized_end=398
+  _ALERT_TYPE._serialized_start=400
+  _ALERT_TYPE._serialized_end=440
+  _ALERT_ACTION._serialized_start=442
+  _ALERT_ACTION._serialized_end=492
+  _ALERT_WHAT._serialized_start=494
+  _ALERT_WHAT._serialized_end=602
+  _MSGRESPONSE._serialized_start=612
+  _MSGRESPONSE._serialized_end=637
+  _EVENT._serialized_start=639
+  _EVENT._serialized_end=750
+  _STATISTICS._serialized_start=753
+  _STATISTICS._serialized_end=1604
+  _STATISTICS_BYPROTOENTRY._serialized_start=1315
+  _STATISTICS_BYPROTOENTRY._serialized_end=1361
+  _STATISTICS_BYADDRESSENTRY._serialized_start=1363
+  _STATISTICS_BYADDRESSENTRY._serialized_end=1411
+  _STATISTICS_BYHOSTENTRY._serialized_start=1413
+  _STATISTICS_BYHOSTENTRY._serialized_end=1458
+  _STATISTICS_BYPORTENTRY._serialized_start=1460
+  _STATISTICS_BYPORTENTRY._serialized_end=1505
+  _STATISTICS_BYUIDENTRY._serialized_start=1507
+  _STATISTICS_BYUIDENTRY._serialized_end=1551
+  _STATISTICS_BYEXECUTABLEENTRY._serialized_start=1553
+  _STATISTICS_BYEXECUTABLEENTRY._serialized_end=1604
+  _PINGREQUEST._serialized_start=1606
+  _PINGREQUEST._serialized_end=1668
+  _PINGREPLY._serialized_start=1670
+  _PINGREPLY._serialized_end=1693
+  _PROCESS._serialized_start=1696
+  _PROCESS._serialized_end=1961
+  _PROCESS_ENVENTRY._serialized_start=1919
+  _PROCESS_ENVENTRY._serialized_end=1961
+  _CONNECTION._serialized_start=1964
+  _CONNECTION._serialized_end=2292
+  _CONNECTION_PROCESSENVENTRY._serialized_start=2243
+  _CONNECTION_PROCESSENVENTRY._serialized_end=2292
+  _OPERATOR._serialized_start=2294
+  _OPERATOR._serialized_end=2402
+  _RULE._serialized_start=2405
+  _RULE._serialized_end=2587
+  _STATEMENTVALUES._serialized_start=2589
+  _STATEMENTVALUES._serialized_end=2634
+  _STATEMENT._serialized_start=2636
+  _STATEMENT._serialized_end=2716
+  _EXPRESSIONS._serialized_start=2718
+  _EXPRESSIONS._serialized_end=2771
+  _FWRULE._serialized_start=2774
+  _FWRULE._serialized_end=2988
+  _FWCHAIN._serialized_start=2991
+  _FWCHAIN._serialized_end=3140
+  _FWCHAINS._serialized_start=3142
+  _FWCHAINS._serialized_end=3219
+  _SYSFIREWALL._serialized_start=3221
+  _SYSFIREWALL._serialized_end=3309
+  _CLIENTCONFIG._serialized_start=3312
+  _CLIENTCONFIG._serialized_end=3508
+  _NOTIFICATION._serialized_start=3511
+  _NOTIFICATION._serialized_end=3698
+  _NOTIFICATIONREPLY._serialized_start=3700
+  _NOTIFICATIONREPLY._serialized_end=3792
+  _UI._serialized_start=4135
+  _UI._serialized_end=4438
+# @@protoc_insertion_point(module_scope)
diff --git a/ui/opensnitch/proto/ui_pb2_grpc.py b/ui/opensnitch/proto/ui_pb2_grpc.py
new file mode 100644 (file)
index 0000000..4e3a786
--- /dev/null
@@ -0,0 +1,198 @@
+# Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
+"""Client and server classes corresponding to protobuf-defined services."""
+import grpc
+
+from . import ui_pb2 as ui__pb2
+
+
+class UIStub(object):
+    """Missing associated documentation comment in .proto file."""
+
+    def __init__(self, channel):
+        """Constructor.
+
+        Args:
+            channel: A grpc.Channel.
+        """
+        self.Ping = channel.unary_unary(
+                '/protocol.UI/Ping',
+                request_serializer=ui__pb2.PingRequest.SerializeToString,
+                response_deserializer=ui__pb2.PingReply.FromString,
+                )
+        self.AskRule = channel.unary_unary(
+                '/protocol.UI/AskRule',
+                request_serializer=ui__pb2.Connection.SerializeToString,
+                response_deserializer=ui__pb2.Rule.FromString,
+                )
+        self.Subscribe = channel.unary_unary(
+                '/protocol.UI/Subscribe',
+                request_serializer=ui__pb2.ClientConfig.SerializeToString,
+                response_deserializer=ui__pb2.ClientConfig.FromString,
+                )
+        self.Notifications = channel.stream_stream(
+                '/protocol.UI/Notifications',
+                request_serializer=ui__pb2.NotificationReply.SerializeToString,
+                response_deserializer=ui__pb2.Notification.FromString,
+                )
+        self.PostAlert = channel.unary_unary(
+                '/protocol.UI/PostAlert',
+                request_serializer=ui__pb2.Alert.SerializeToString,
+                response_deserializer=ui__pb2.MsgResponse.FromString,
+                )
+
+
+class UIServicer(object):
+    """Missing associated documentation comment in .proto file."""
+
+    def Ping(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def AskRule(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def Subscribe(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def Notifications(self, request_iterator, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+    def PostAlert(self, request, context):
+        """Missing associated documentation comment in .proto file."""
+        context.set_code(grpc.StatusCode.UNIMPLEMENTED)
+        context.set_details('Method not implemented!')
+        raise NotImplementedError('Method not implemented!')
+
+
+def add_UIServicer_to_server(servicer, server):
+    rpc_method_handlers = {
+            'Ping': grpc.unary_unary_rpc_method_handler(
+                    servicer.Ping,
+                    request_deserializer=ui__pb2.PingRequest.FromString,
+                    response_serializer=ui__pb2.PingReply.SerializeToString,
+            ),
+            'AskRule': grpc.unary_unary_rpc_method_handler(
+                    servicer.AskRule,
+                    request_deserializer=ui__pb2.Connection.FromString,
+                    response_serializer=ui__pb2.Rule.SerializeToString,
+            ),
+            'Subscribe': grpc.unary_unary_rpc_method_handler(
+                    servicer.Subscribe,
+                    request_deserializer=ui__pb2.ClientConfig.FromString,
+                    response_serializer=ui__pb2.ClientConfig.SerializeToString,
+            ),
+            'Notifications': grpc.stream_stream_rpc_method_handler(
+                    servicer.Notifications,
+                    request_deserializer=ui__pb2.NotificationReply.FromString,
+                    response_serializer=ui__pb2.Notification.SerializeToString,
+            ),
+            'PostAlert': grpc.unary_unary_rpc_method_handler(
+                    servicer.PostAlert,
+                    request_deserializer=ui__pb2.Alert.FromString,
+                    response_serializer=ui__pb2.MsgResponse.SerializeToString,
+            ),
+    }
+    generic_handler = grpc.method_handlers_generic_handler(
+            'protocol.UI', rpc_method_handlers)
+    server.add_generic_rpc_handlers((generic_handler,))
+
+
+ # This class is part of an EXPERIMENTAL API.
+class UI(object):
+    """Missing associated documentation comment in .proto file."""
+
+    @staticmethod
+    def Ping(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/protocol.UI/Ping',
+            ui__pb2.PingRequest.SerializeToString,
+            ui__pb2.PingReply.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def AskRule(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/protocol.UI/AskRule',
+            ui__pb2.Connection.SerializeToString,
+            ui__pb2.Rule.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def Subscribe(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/protocol.UI/Subscribe',
+            ui__pb2.ClientConfig.SerializeToString,
+            ui__pb2.ClientConfig.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def Notifications(request_iterator,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.stream_stream(request_iterator, target, '/protocol.UI/Notifications',
+            ui__pb2.NotificationReply.SerializeToString,
+            ui__pb2.Notification.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
+
+    @staticmethod
+    def PostAlert(request,
+            target,
+            options=(),
+            channel_credentials=None,
+            call_credentials=None,
+            insecure=False,
+            compression=None,
+            wait_for_ready=None,
+            timeout=None,
+            metadata=None):
+        return grpc.experimental.unary_unary(request, target, '/protocol.UI/PostAlert',
+            ui__pb2.Alert.SerializeToString,
+            ui__pb2.MsgResponse.FromString,
+            options, channel_credentials,
+            insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
diff --git a/ui/opensnitch/res/__init__.py b/ui/opensnitch/res/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ui/opensnitch/res/firewall.ui b/ui/opensnitch/res/firewall.ui
new file mode 100644 (file)
index 0000000..21a7dec
--- /dev/null
@@ -0,0 +1,520 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>440</width>
+    <height>463</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Firewall</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="styleSheet">
+      <string notr="true">QTabBar {
+   alignment: center;
+}</string>
+     </property>
+     <property name="tabPosition">
+      <enum>QTabWidget::South</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="tabMain">
+      <attribute name="title">
+       <string/>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <property name="leftMargin">
+          <number>10</number>
+         </property>
+         <property name="topMargin">
+          <number>5</number>
+         </property>
+         <property name="rightMargin">
+          <number>10</number>
+         </property>
+         <property name="bottomMargin">
+          <number>10</number>
+         </property>
+         <item>
+          <widget class="QLabel" name="label_2">
+           <property name="text">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:14pt; font-weight:600;&quot;&gt;Firewall&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_3">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QSlider" name="sliderFwEnable">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="maximumSize">
+            <size>
+             <width>48</width>
+             <height>16777215</height>
+            </size>
+           </property>
+           <property name="styleSheet">
+            <string notr="true">QSlider::groove:horizontal { 
+       background-color: transparent;
+       border: 0px solid #424242; 
+       height: 20px; 
+       border-radius: 4px;
+}
+
+QSlider::handle:horizontal { 
+       background-color: rgb(154, 153, 150); 
+       border: 1px solid rgb(119, 118, 123);
+       width: 20px; 
+       height: 20px;
+       line-height: 20px; /* do not delete this */
+    /*margin-left: -5px;*/
+       margin-top: -2px; 
+       margin-bottom: -2px; 
+       border-radius: 5px; 
+}
+
+QSlider::handle:horizontal:hover {
+  background-color: rgb(79, 91, 98); 
+  border-radius: 5px; 
+}
+
+QSlider::add-page:horizontal {
+       border-radius: 4px;
+    background: rgb(237, 51, 59);
+    border: 1px solid rgb(192, 28, 40);
+}
+
+QSlider::sub-page:horizontal {
+       border-radius: 4px;
+    background: rgb(139, 195, 74);
+    border: 1px solid rgb(67, 190, 24);
+}</string>
+           </property>
+           <property name="maximum">
+            <number>1</number>
+           </property>
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="invertedAppearance">
+            <bool>false</bool>
+           </property>
+           <property name="tickPosition">
+            <enum>QSlider::TicksBothSides</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QLabel" name="lblStatusIcon">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::AutoText</enum>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+         <property name="openExternalLinks">
+          <bool>true</bool>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <spacer name="horizontalSpacer_4">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="Line" name="line">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="minimumSize">
+            <size>
+             <width>96</width>
+             <height>0</height>
+            </size>
+           </property>
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_5">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <widget class="QLabel" name="lblFwStatus">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="styleSheet">
+          <string notr="true">color: rgb(237, 51, 59);</string>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QGroupBox" name="policiesBox">
+         <property name="title">
+          <string/>
+         </property>
+         <property name="flat">
+          <bool>true</bool>
+         </property>
+         <layout class="QGridLayout" name="gridLayout">
+          <property name="leftMargin">
+           <number>15</number>
+          </property>
+          <property name="topMargin">
+           <number>5</number>
+          </property>
+          <property name="rightMargin">
+           <number>15</number>
+          </property>
+          <property name="bottomMargin">
+           <number>0</number>
+          </property>
+          <item row="4" column="0" colspan="2">
+           <widget class="Line" name="line_2">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <property name="orientation">
+             <enum>Qt::Horizontal</enum>
+            </property>
+           </widget>
+          </item>
+          <item row="0" column="0">
+           <widget class="QLabel" name="lblProfile">
+            <property name="text">
+             <string>Profile</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="1">
+           <widget class="QComboBox" name="comboInput">
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <item>
+             <property name="text">
+              <string>Allow</string>
+             </property>
+             <property name="icon">
+              <iconset theme="emblem-default">
+               <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Deny</string>
+             </property>
+             <property name="icon">
+              <iconset theme="process-stop">
+               <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="2" column="0">
+           <widget class="QLabel" name="label_4">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="text">
+             <string>Outbound</string>
+            </property>
+           </widget>
+          </item>
+          <item row="1" column="0">
+           <widget class="QLabel" name="label_3">
+            <property name="text">
+             <string>Inbound</string>
+            </property>
+           </widget>
+          </item>
+          <item row="2" column="1">
+           <widget class="QComboBox" name="comboOutput">
+            <property name="enabled">
+             <bool>false</bool>
+            </property>
+            <property name="sizePolicy">
+             <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+              <horstretch>0</horstretch>
+              <verstretch>0</verstretch>
+             </sizepolicy>
+            </property>
+            <item>
+             <property name="text">
+              <string>Allow</string>
+             </property>
+             <property name="icon">
+              <iconset theme="emblem-default">
+               <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+             </property>
+            </item>
+            <item>
+             <property name="text">
+              <string>Deny</string>
+             </property>
+             <property name="icon">
+              <iconset theme="process-stop">
+               <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+             </property>
+            </item>
+           </widget>
+          </item>
+          <item row="0" column="1">
+           <widget class="QComboBox" name="comboProfile"/>
+          </item>
+          <item row="5" column="0" colspan="2">
+           <layout class="QGridLayout" name="gridLayout_2">
+            <property name="topMargin">
+             <number>5</number>
+            </property>
+            <property name="bottomMargin">
+             <number>5</number>
+            </property>
+            <item row="0" column="0">
+             <widget class="QPushButton" name="cmdAllowINService">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="toolTip">
+               <string>Allow inbound connections to a port</string>
+              </property>
+              <property name="text">
+               <string>Allow service (IN)</string>
+              </property>
+              <property name="icon">
+               <iconset theme="go-down">
+                <normaloff>.</normaloff>.</iconset>
+              </property>
+              <property name="flat">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item row="0" column="1">
+             <widget class="QPushButton" name="cmdAllowOUTService">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="toolTip">
+               <string>Exclude outbound connections to a port from being intercepted</string>
+              </property>
+              <property name="layoutDirection">
+               <enum>Qt::RightToLeft</enum>
+              </property>
+              <property name="styleSheet">
+               <string notr="true">QPushButton { text-align: right; }</string>
+              </property>
+              <property name="text">
+               <string>Allow service (OUT)</string>
+              </property>
+              <property name="icon">
+               <iconset theme="go-up">
+                <normaloff>.</normaloff>.</iconset>
+              </property>
+              <property name="flat">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+            <item row="1" column="0" colspan="2">
+             <widget class="QPushButton" name="cmdNewRule">
+              <property name="sizePolicy">
+               <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+                <horstretch>0</horstretch>
+                <verstretch>0</verstretch>
+               </sizepolicy>
+              </property>
+              <property name="text">
+               <string>New rule</string>
+              </property>
+              <property name="icon">
+               <iconset theme="document-new">
+                <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+              </property>
+              <property name="flat">
+               <bool>true</bool>
+              </property>
+             </widget>
+            </item>
+           </layout>
+          </item>
+         </layout>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_5">
+     <item>
+      <widget class="QLabel" name="statusLabel">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QPushButton" name="cmdHelp">
+       <property name="text">
+        <string/>
+       </property>
+       <property name="icon">
+        <iconset theme="help-browser">
+         <normaloff>.</normaloff>.</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdClose">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string>Close</string>
+       </property>
+       <property name="icon">
+        <iconset theme="window-close">
+         <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>sliderFwEnable</tabstop>
+  <tabstop>comboInput</tabstop>
+  <tabstop>comboOutput</tabstop>
+  <tabstop>cmdClose</tabstop>
+  <tabstop>tabWidget</tabstop>
+ </tabstops>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/res/firewall_rule.ui b/ui/opensnitch/res/firewall_rule.ui
new file mode 100644 (file)
index 0000000..728d467
--- /dev/null
@@ -0,0 +1,479 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>484</width>
+    <height>600</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Firewall rule</string>
+  </property>
+  <property name="windowIcon">
+   <iconset>
+    <normaloff>../../../../../../.designer/backup/icon-white.svg</normaloff>../../../../../../.designer/backup/icon-white.svg</iconset>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <widget class="QLabel" name="label_7">
+       <property name="text">
+        <string>Node</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="comboNodes"/>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QCheckBox" name="checkEnable">
+     <property name="text">
+      <string>Enable</string>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_3">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetMaximumSize</enum>
+     </property>
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Description</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="lineDescription">
+       <property name="clearButtonEnabled">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="styleSheet">
+      <string notr="true">QTabBar {
+   alignment: center;
+}</string>
+     </property>
+     <property name="tabPosition">
+      <enum>QTabWidget::South</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="elideMode">
+      <enum>Qt::ElideRight</enum>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <property name="tabBarAutoHide">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="tabSimple">
+      <attribute name="title">
+       <string>Simple</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_2">
+       <item>
+        <widget class="QToolBox" name="toolBoxSimple">
+         <property name="currentIndex">
+          <number>0</number>
+         </property>
+         <property name="tabSpacing">
+          <number>0</number>
+         </property>
+         <widget class="QWidget" name="page1">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>448</width>
+            <height>200</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string/>
+          </attribute>
+         </widget>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="lblExcludeTip">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <widget class="QWidget" name="hboxAdvanced" native="true">
+     <layout class="QHBoxLayout" name="horizontalLayout_5">
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QPushButton" name="cmdAddStatement">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Add new condition</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="list-add">
+          <normaloff>.</normaloff>.</iconset>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="cmdDelStatement">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="toolTip">
+         <string>Remove selected condition</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="list-remove">
+          <normaloff>.</normaloff>.</iconset>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="QFrame" name="frameDirection">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Plain</enum>
+     </property>
+     <property name="lineWidth">
+      <number>0</number>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_6">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="label_3">
+        <property name="text">
+         <string>Direction</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="comboDirection">
+        <item>
+         <property name="text">
+          <string>IN</string>
+         </property>
+         <property name="icon">
+          <iconset theme="go-down">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>OUT</string>
+         </property>
+         <property name="icon">
+          <iconset theme="go-up">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>FORWARD</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>PREROUTING</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>POSTROUTING</string>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Action</string>
+        </property>
+        <property name="alignment">
+         <set>Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft</set>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="comboVerdict">
+        <item>
+         <property name="text">
+          <string>ACCEPT</string>
+         </property>
+         <property name="icon">
+          <iconset theme="emblem-default">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>DROP</string>
+         </property>
+         <property name="icon">
+          <iconset theme="window-close">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>REJECT</string>
+         </property>
+         <property name="icon">
+          <iconset theme="edit-delete">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>RETURN</string>
+         </property>
+         <property name="icon">
+          <iconset theme="edit-undo">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>QUEUE</string>
+         </property>
+         <property name="icon">
+          <iconset theme="go-next"/>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>DNAT</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>SNAT</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>REDIRECT</string>
+         </property>
+         <property name="icon">
+          <iconset theme="edit-redo"/>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item>
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <widget class="QComboBox" name="comboVerdictParms"/>
+        </item>
+        <item>
+         <widget class="QLineEdit" name="lineVerdictParms">
+          <property name="toolTip">
+           <string>depending on the Action (i.e.: target), the syntaxis of the parameters will vary.
+Some examples:
+
+QUEUE -&gt; num 0 (or 1, 2, ...)
+REDIRECT, TPROXY, DNAT, SNAT, MASQUERADE:
+ to :22
+ to 192.168.1.254:8080
+ to 192.168.1.254
+ to 1024-2048 (masquerade)</string>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLabel" name="statusLabel">
+     <property name="text">
+      <string/>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="helpButton">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+       <property name="icon">
+        <iconset theme="help-browser">
+         <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+       </property>
+       <property name="flat">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdClose">
+       <property name="text">
+        <string>Close</string>
+       </property>
+       <property name="icon">
+        <iconset theme="window-close">
+         <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdReset">
+       <property name="text">
+        <string>Clear</string>
+       </property>
+       <property name="icon">
+        <iconset theme="reload">
+         <normaloff>.</normaloff>.</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdDelete">
+       <property name="text">
+        <string>Delete</string>
+       </property>
+       <property name="icon">
+        <iconset theme="edit-delete">
+         <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdSave">
+       <property name="text">
+        <string>Save</string>
+       </property>
+       <property name="icon">
+        <iconset theme="document-save">
+         <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdAdd">
+       <property name="text">
+        <string>Add</string>
+       </property>
+       <property name="icon">
+        <iconset theme="list-add">
+         <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/res/icon-alert.png b/ui/opensnitch/res/icon-alert.png
new file mode 100644 (file)
index 0000000..77cb571
Binary files /dev/null and b/ui/opensnitch/res/icon-alert.png differ
diff --git a/ui/opensnitch/res/icon-off.png b/ui/opensnitch/res/icon-off.png
new file mode 100644 (file)
index 0000000..e33aced
Binary files /dev/null and b/ui/opensnitch/res/icon-off.png differ
diff --git a/ui/opensnitch/res/icon-pause.png b/ui/opensnitch/res/icon-pause.png
new file mode 100644 (file)
index 0000000..436df98
Binary files /dev/null and b/ui/opensnitch/res/icon-pause.png differ
diff --git a/ui/opensnitch/res/icon-pause.svg b/ui/opensnitch/res/icon-pause.svg
new file mode 100644 (file)
index 0000000..9f61111
--- /dev/null
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   width="512"
+   height="512"
+   viewBox="0 0 135.46667 135.46667"
+   version="1.1"
+   id="svg8"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="icon-pause.svg"
+   inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/opensnitch/res/icon-pause.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.8520834"
+     inkscape:cx="92.868389"
+     inkscape:cy="235.9505"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="false"
+     inkscape:window-width="1600"
+     inkscape:window-height="847"
+     inkscape:window-x="0"
+     inkscape:window-y="204"
+     inkscape:window-maximized="1"
+     units="px"
+     inkscape:document-rotation="0"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:pagecheckerboard="0">
+    <sodipodi:guide
+       position="45.643586,19.473337"
+       orientation="0,-1"
+       id="guide858" />
+    <sodipodi:guide
+       position="48.574825,118.50736"
+       orientation="0,-1"
+       id="guide860" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Capa 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-284.30001)">
+    <path
+       style="fill:#232629;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.88352px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       d="m 24.756487,346.52813 9.319538,-24.69115 25.892395,-5.7321 24.204196,-12.72968 25.173834,11.71117 4.05241,24.92736 16.3729,12.59899 3.85152,17.78296 -6.72818,16.22266 -13.39906,8.50947 -91.980588,0.9444 -15.4585065,-11.35245 -3.13783,-17.10855 7.8853195,-15.623 z"
+       id="path1481"
+       sodipodi:nodetypes="ccccccccccccccc"
+       inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png"
+       inkscape:export-xdpi="96"
+       inkscape:export-ydpi="96" />
+    <path
+       style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.260914;stroke-opacity:1"
+       d="M 25.617431,400.30128 C 13.956756,398.62993 4.455433,390.16178 1.3179549,378.64484 c -0.61010168,-2.23995 -0.68141103,-2.97449 -0.68141103,-7.02809 0,-4.05349 0.0709898,-4.78804 0.68141103,-7.02767 1.4422687,-5.29433 4.0049287,-9.7113 7.7967237,-13.43793 3.1792184,-3.12477 7.6935334,-5.90721 11.3127154,-6.97315 l 1.011141,-0.29572 0.159592,-2.88031 c 0.699721,-12.63666 9.525535,-23.14365 22.243579,-26.48047 2.251529,-0.59144 3.06051,-0.67035 7.018915,-0.67878 2.525469,-0.006 4.987882,0.11997 5.653762,0.28414 1.179544,0.29572 1.179787,0.29572 1.94729,-0.66193 1.527585,-1.90257 4.987766,-4.99031 7.28526,-6.50045 5.401898,-3.5513 10.887485,-5.3302 17.382981,-5.63665 5.770238,-0.26941 10.984996,0.78506 16.16673,3.27379 7.128836,3.42523 12.181556,8.39142 15.666736,15.39805 2.50603,5.03807 3.59513,10.17644 3.34371,15.77554 l -0.13051,2.91272 2.13481,1.38154 c 1.17413,0.7598 3.3948,2.61049 4.93483,4.112 8.71561,8.49782 11.93955,20.33805 8.75495,32.15396 -1.29904,4.81951 -4.90315,10.89827 -8.63914,14.56995 -3.56956,3.50836 -9.80593,7.14911 -14.36425,8.38564 -4.82636,1.30967 -4.22578,1.29325 -45.398785,1.26083 -21.133453,-0.021 -39.125022,-0.13154 -39.981254,-0.25467 z m 81.947239,-8.71712 c 5.8504,-1.53751 11.05542,-5.03703 14.56544,-9.79276 1.44781,-1.96191 3.2449,-5.86701 3.9156,-8.50866 0.82755,-3.25884 0.82928,-8.34365 0.004,-11.5926 -1.99124,-7.83999 -8.14631,-14.63425 -15.64619,-17.27125 -1.49953,-0.52724 -2.28209,-0.9303 -2.20209,-1.1355 2.47536,-6.33996 2.20303,-13.3187 -0.75958,-19.46544 -2.59443,-5.38335 -6.55609,-9.27909 -12.007089,-11.8076 -3.90889,-1.81322 -6.232969,-2.31309 -10.75536,-2.31309 -3.127696,0 -4.219681,0.10629 -6.053166,0.58617 -7.241285,1.89288 -13.31075,6.68923 -16.465647,13.01161 l -0.732485,1.46794 -1.204478,-0.5988 c -2.240975,-1.11298 -5.542791,-1.97328 -8.178424,-2.13124 -9.130628,-0.54723 -18.012897,5.39587 -20.93206,14.00441 -1.653072,4.87486 -1.486231,9.52955 0.524816,14.64108 0.134869,0.34097 -0.08673,0.38411 -1.535235,0.29256 -2.040978,-0.12839 -5.359579,0.4904 -7.966056,1.4852 -5.358562,2.0439 -10.218999,7.06398 -12.038096,12.43418 -1.6865107,4.9784 -1.5773642,9.50883 0.346691,14.39252 2.080429,5.27991 7.182215,10.04153 12.692712,11.84623 1.10934,0.36201 2.648952,0.76296 3.421359,0.8924 0.794802,0.13259 18.196341,0.21573 40.092031,0.19047 l 38.687677,-0.0421 z"
+       id="path826"
+       inkscape:connector-curvature="0"
+       sodipodi:nodetypes="ccsccccccccccccsccccccccccccccscccsccccsscccccccccc" />
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.6842px;line-height:47.9278px;font-family:Korataki;-inkscape-font-specification:Korataki;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.91711px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="34.512127"
+       y="456.6312"
+       id="text841"
+       transform="scale(1.1975484,0.83503932)"><tspan
+         sodipodi:role="line"
+         id="tspan839"
+         x="34.512127"
+         y="456.6312"
+         style="fill:#ffffff;stroke-width:1.91711px">I</tspan></text>
+    <text
+       xml:space="preserve"
+       style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:76.6842px;line-height:47.9278px;font-family:Korataki;-inkscape-font-specification:Korataki;letter-spacing:0px;word-spacing:0px;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1.91711px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+       x="61.164921"
+       y="456.6312"
+       id="text841-7"
+       transform="scale(1.1975484,0.83503932)"><tspan
+         sodipodi:role="line"
+         id="tspan839-6"
+         x="61.164921"
+         y="456.6312"
+         style="fill:#ffffff;stroke-width:1.91711px">I</tspan></text>
+  </g>
+</svg>
diff --git a/ui/opensnitch/res/icon-red.png b/ui/opensnitch/res/icon-red.png
new file mode 100644 (file)
index 0000000..d495251
Binary files /dev/null and b/ui/opensnitch/res/icon-red.png differ
diff --git a/ui/opensnitch/res/icon-white.png b/ui/opensnitch/res/icon-white.png
new file mode 100644 (file)
index 0000000..dae1587
Binary files /dev/null and b/ui/opensnitch/res/icon-white.png differ
diff --git a/ui/opensnitch/res/icon-white.svg b/ui/opensnitch/res/icon-white.svg
new file mode 100644 (file)
index 0000000..a0e5a53
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="512"
+   height="512"
+   viewBox="0 0 135.46667 135.46667"
+   version="1.1"
+   id="svg8"
+   inkscape:version="1.1.1 (3bf5ae0d25, 2021-09-20)"
+   sodipodi:docname="icon-white.svg"
+   inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.9899495"
+     inkscape:cx="288.90363"
+     inkscape:cy="223.24371"
+     inkscape:document-units="px"
+     inkscape:current-layer="g3307"
+     showgrid="false"
+     inkscape:window-width="1600"
+     inkscape:window-height="847"
+     inkscape:window-x="0"
+     inkscape:window-y="204"
+     inkscape:window-maximized="1"
+     units="px"
+     inkscape:pagecheckerboard="0"
+     showguides="true"
+     inkscape:guide-bbox="true" />
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Capa 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-284.30001)">
+    <g
+       id="g3307"
+       transform="matrix(10.756234,0,0,10.756234,-0.76070676,-2776.4763)">
+      <path
+         style="fill:#232629;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.268079px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 2.4759494,290.29453 0.8664313,-2.29552 2.4071989,-0.53291 2.2502482,-1.18347 2.3403932,1.08878 0.37675,2.31748 1.522178,1.17132 0.358073,1.65327 -0.625514,1.50821 -1.245702,0.79112 -8.5513735,0.0878 -1.43716703,-1.05543 -0.29172201,-1.59057 0.73309294,-1.45246 z"
+         id="path1481"
+         sodipodi:nodetypes="ccccccccccccccc"
+         inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png"
+         inkscape:export-xdpi="96"
+         inkscape:export-ydpi="96" />
+      <path
+         style="fill:#fbffff;fill-opacity:1;stroke:none;stroke-width:0.0245664;stroke-opacity:1"
+         d="m 2.4221019,295.34968 c -1.0878659,-0.15882 -1.97427807,-0.9635 -2.26698456,-2.05789 -0.05691855,-0.21285 -0.06357126,-0.28265 -0.06357126,-0.66784 0,-0.38518 0.0066228,-0.45498 0.06357126,-0.6678 0.13455437,-0.50309 0.37363404,-0.92281 0.72738412,-1.27693 0.29660044,-0.29693 0.71775704,-0.56133 1.05540334,-0.66262 l 0.094333,-0.0281 0.014889,-0.2737 c 0.065279,-1.20079 0.888671,-2.19921 2.0751826,-2.51629 0.2100531,-0.0562 0.2855258,-0.0637 0.6548195,-0.0645 0.2356099,-5.9e-4 0.4653373,0.0114 0.5274596,0.027 0.1100438,0.0281 0.1100666,0.0281 0.1816696,-0.0629 0.1425139,-0.18079 0.4653264,-0.4742 0.6796678,-0.6177 0.5039623,-0.33746 1.0157321,-0.5065 1.6217201,-0.53562 0.538326,-0.0256 1.0248294,0.0746 1.5082517,0.31109 0.665074,0.32548 1.1364593,0.79739 1.4616053,1.46319 0.233795,0.47874 0.335401,0.96701 0.311946,1.49906 l -0.01218,0.27678 0.199164,0.13128 c 0.109539,0.0722 0.316714,0.24806 0.460388,0.39074 0.813111,0.8075 1.113884,1.93261 0.816781,3.05541 -0.121193,0.45797 -0.457432,1.0356 -0.805977,1.3845 -0.333016,0.33338 -0.91483,0.67934 -1.34009,0.79684 -0.4502688,0.12445 -0.3942394,0.12289 -4.2354147,0.11981 -1.971615,-0.002 -3.6501125,-0.0125 -3.7299933,-0.0242 z m 7.6451481,-0.82834 c 0.545805,-0.1461 1.0314,-0.47864 1.358863,-0.93055 0.13507,-0.18643 0.302726,-0.55751 0.365299,-0.80853 0.07721,-0.30967 0.07737,-0.79285 3.72e-4,-1.10158 -0.185769,-0.74499 -0.759998,-1.39061 -1.459688,-1.64119 -0.139897,-0.0501 -0.212904,-0.0884 -0.205441,-0.1079 0.230936,-0.60245 0.205529,-1.2656 -0.07086,-1.84969 -0.2420436,-0.51155 -0.6116411,-0.88174 -1.1201832,-1.12201 -0.3646743,-0.1723 -0.5814957,-0.2198 -1.0034057,-0.2198 -0.291794,0 -0.3936692,0.0101 -0.5647214,0.0557 -0.6755653,0.17987 -1.2418071,0.63564 -1.5361388,1.23642 l -0.068336,0.13949 -0.1123702,-0.0569 c -0.2090683,-0.10576 -0.5171066,-0.18751 -0.7629941,-0.20252 -0.8518289,-0.052 -1.6804874,0.51274 -1.9528265,1.33076 -0.1542208,0.46323 -0.1386557,0.90554 0.048962,1.39126 0.012582,0.0324 -0.00809,0.0365 -0.1432276,0.0278 -0.1904101,-0.0122 -0.5000142,0.0466 -0.7431816,0.14113 -0.4999194,0.19422 -0.9533668,0.67125 -1.12307682,1.18155 -0.15734055,0.47307 -0.14715788,0.90357 0.032344,1.36764 0.1940906,0.50172 0.6700544,0.95419 1.1841482,1.12568 0.1034943,0.0344 0.2471302,0.0725 0.3191908,0.0848 0.07415,0.0126 1.6976013,0.0205 3.7403281,0.0181 l 3.6093097,-0.004 z m -5.2655768,-1.2975 C 4.2750097,292.90743 3.844102,292.6375 3.844102,292.62395 c 0,-0.0137 0.4309077,-0.28347 0.9575712,-0.59989 l 0.9575723,-0.5752 0.00674,0.39089 0.00674,0.39094 h 1.5707862 1.5707918 v 0.39326 0.39327 H 7.34349 5.772697 l -0.00674,0.39094 -0.00674,0.39093 z m 2.1483996,-2.17014 v -0.39612 H 5.3786739 3.807272 v -0.39326 -0.39328 h 1.570792 1.5707932 l 0.00674,-0.39093 0.00674,-0.39093 0.9575575,0.57524 c 0.5266596,0.3164 0.9606572,0.58453 0.9644402,0.59591 0.00375,0.0115 -0.4050204,0.26764 -0.908454,0.56956 -0.5034315,0.30187 -0.940193,0.56487 -0.970577,0.58441 l -0.055248,0.0354 z"
+         id="path826"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/ui/opensnitch/res/icon.png b/ui/opensnitch/res/icon.png
new file mode 100644 (file)
index 0000000..786e9a0
Binary files /dev/null and b/ui/opensnitch/res/icon.png differ
diff --git a/ui/opensnitch/res/preferences.ui b/ui/opensnitch/res/preferences.ui
new file mode 100644 (file)
index 0000000..bbf8193
--- /dev/null
@@ -0,0 +1,2290 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>PreferencesDialog</class>
+ <widget class="QDialog" name="PreferencesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>626</width>
+    <height>534</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Preferences</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout">
+   <item row="2" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QPushButton" name="helpButton">
+       <property name="mouseTracking">
+        <bool>true</bool>
+       </property>
+       <property name="toolTip">
+        <string/>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+       <property name="icon">
+        <iconset theme="help-browser">
+         <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+       </property>
+       <property name="flat">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_4">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cancelButton">
+       <property name="text">
+        <string>Close</string>
+       </property>
+       <property name="icon">
+        <iconset theme="window-close">
+         <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="applyButton">
+       <property name="text">
+        <string>Apply</string>
+       </property>
+       <property name="icon">
+        <iconset theme="document-save">
+         <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="acceptButton">
+       <property name="text">
+        <string>Save</string>
+       </property>
+       <property name="icon">
+        <iconset theme="emblem-default">
+         <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="0" column="0">
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="tabPosition">
+      <enum>QTabWidget::North</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <widget class="QWidget" name="tab">
+      <property name="sizePolicy">
+       <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+        <horstretch>0</horstretch>
+        <verstretch>0</verstretch>
+       </sizepolicy>
+      </property>
+      <attribute name="title">
+       <string>Pop-ups</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="1" column="0" colspan="3">
+        <widget class="QLabel" name="label">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This timeout is the countdown you see when a pop-up dialog is shown.&lt;/p&gt;&lt;p&gt;If the pop-up is not answered, the default options will be applied.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="text">
+          <string>Default timeout</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="3">
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="QPushButton" name="cmdTimeoutUp">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="list-add">
+             <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QSpinBox" name="spinUITimeout">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+           <property name="buttonSymbols">
+            <enum>QAbstractSpinBox::NoButtons</enum>
+           </property>
+           <property name="accelerated">
+            <bool>true</bool>
+           </property>
+           <property name="maximum">
+            <number>100</number>
+           </property>
+           <property name="value">
+            <number>30</number>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="cmdTimeoutDown">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="list-remove">
+             <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="0" column="3">
+        <widget class="QCheckBox" name="popupsCheck">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="0" colspan="3">
+        <widget class="QLabel" name="label_16">
+         <property name="text">
+          <string>Disable pop-ups, only display a notification</string>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0" colspan="4">
+        <widget class="QToolBox" name="toolBox">
+         <widget class="QWidget" name="page_7">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>586</width>
+            <height>306</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>Default options</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_5" rowstretch="0,0,0,0" columnstretch="1,0">
+           <item row="1" column="1">
+            <widget class="QComboBox" name="comboUIDuration">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <item>
+              <property name="text">
+               <string>once</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>30s</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>5m</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>15m</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>30m</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>1h</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>until reboot</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>forever</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_2">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Pop-up default action.&lt;/p&gt;&lt;p&gt;When a new outgoing connection is about to be established, this action will be selected by default, so if the timeout fires, this is the option that will be applied.&lt;/p&gt;&lt;p&gt;While a pop-up is asking the user to allow or deny a connection:&lt;/p&gt;&lt;p&gt;1. the daemon's default action will be applied (see Nodes tab).&lt;/p&gt;&lt;p&gt;2. known connections are allowed or denied based on the rules defined by the user.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Action</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="label_4">
+             <property name="text">
+              <string>Default position on screen</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_3">
+             <property name="toolTip">
+              <string>Pop-up default duration</string>
+             </property>
+             <property name="text">
+              <string>Duration</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QComboBox" name="comboUITarget">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <item>
+              <property name="text">
+               <string>by executable</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>by command line</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>by destination port</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>by destination ip</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>by user id</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>by PID</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QComboBox" name="comboUIDialogPos">
+             <property name="enabled">
+              <bool>true</bool>
+             </property>
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <item>
+              <property name="text">
+               <string>center</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>top right</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>bottom right</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>top left</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>bottom left</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QComboBox" name="comboUIAction">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <item>
+              <property name="text">
+               <string>deny</string>
+              </property>
+              <property name="icon">
+               <iconset theme="emblem-important">
+                <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>allow</string>
+              </property>
+              <property name="icon">
+               <iconset theme="emblem-default">
+                <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>reject</string>
+              </property>
+              <property name="icon">
+               <iconset theme="window-close">
+                <normaloff>.</normaloff>.</iconset>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="label_5">
+             <property name="text">
+              <string>Default target</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="page_8">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>586</width>
+            <height>306</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>More</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_14">
+           <item row="2" column="0" colspan="2">
+            <layout class="QHBoxLayout" name="horizontalLayout_2" stretch="0,0,0">
+             <item>
+              <widget class="QCheckBox" name="uidCheck">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="toolTip">
+                <string>If checked, this field will be selected when a pop-up is displayed</string>
+               </property>
+               <property name="text">
+                <string>User ID</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QCheckBox" name="dstPortCheck">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="toolTip">
+                <string>If checked, this field will be selected when a pop-up is displayed</string>
+               </property>
+               <property name="text">
+                <string>Destination port</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QCheckBox" name="dstIPCheck">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="toolTip">
+                <string>If checked, this field will be selected when a pop-up is displayed</string>
+               </property>
+               <property name="text">
+                <string>Destination IP</string>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_18">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default when a new pop-up appears, in its simplest form, you'll be able to filter connections or applications by one property of the connection (executable, port, IP, etc).&lt;/p&gt;&lt;p&gt;With these options, you can choose multiple fields to filter connections for.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Filter connections also by:</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_17">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="toolTip">
+              <string>The advanced view allows you to easily select multiple fields to filter connections</string>
+             </property>
+             <property name="text">
+              <string>Show advanced view by default</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QCheckBox" name="showAdvancedCheck">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, the pop-ups will be displayed with the advanced view active.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_4">
+      <attribute name="title">
+       <string>UI</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_6">
+       <item row="0" column="0">
+        <widget class="QToolBox" name="toolBox_2">
+         <property name="currentIndex">
+          <number>0</number>
+         </property>
+         <widget class="QWidget" name="page_5">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>586</width>
+            <height>301</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>General</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_16">
+           <item row="10" column="2" colspan="2">
+            <widget class="QLineEdit" name="lineUIScreenFactor">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="toolTip">
+              <string>Use numbers to define a global scale factor for the whole application:
+1, 1.2, 1.5, 2, etc ...
+
+Use ; to define multiple screens: 1;1.5 etc...</string>
+             </property>
+             <property name="placeholderText">
+              <string>ex: 1, 1.25, 1.5, 2, ...</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="2" colspan="2">
+            <layout class="QHBoxLayout" name="horizontalLayout_8">
+             <property name="spacing">
+              <number>0</number>
+             </property>
+             <item>
+              <widget class="QPushButton" name="cmdRefreshUIUp">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-add">
+                 <normaloff>../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../.designer/backup</iconset>
+               </property>
+               <property name="flat">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QSpinBox" name="spinUIRefresh">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+               <property name="buttonSymbols">
+                <enum>QAbstractSpinBox::NoButtons</enum>
+               </property>
+               <property name="accelerated">
+                <bool>true</bool>
+               </property>
+               <property name="maximum">
+                <number>100</number>
+               </property>
+               <property name="value">
+                <number>1</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QPushButton" name="cmdRefreshUIDown">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Fixed" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-remove">
+                 <normaloff>../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../.designer/backup</iconset>
+               </property>
+               <property name="flat">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="3" column="2" colspan="2">
+            <widget class="QComboBox" name="comboUITheme">
+             <item>
+              <property name="text">
+               <string>System</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="12" column="0" colspan="2">
+            <widget class="QCheckBox" name="checkAutostart">
+             <property name="toolTip">
+              <string>By default the GUI is started when login</string>
+             </property>
+             <property name="text">
+              <string>Autostart the GUI upon login</string>
+             </property>
+             <property name="checked">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0" colspan="2">
+            <widget class="QLabel" name="label_27">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string>Refresh interval (seconds)</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="11" column="0" colspan="2">
+            <widget class="QLabel" name="labelUIDensity">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string>Theme density scale</string>
+             </property>
+            </widget>
+           </item>
+           <item row="7" column="0" colspan="4">
+            <widget class="QCheckBox" name="checkUIAutoScreen">
+             <property name="text">
+              <string>Auto screen scale factor</string>
+             </property>
+             <property name="checked">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="label_21">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string>Theme</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="2" colspan="2">
+            <widget class="QComboBox" name="comboUILang"/>
+           </item>
+           <item row="10" column="0">
+            <widget class="QLabel" name="labelUIScreenFactor">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Scale factor (use ; for multiple displays) &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems#gui-size-problems-on-4k-monitors&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;More information&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+            </widget>
+           </item>
+           <item row="11" column="2" colspan="2">
+            <layout class="QHBoxLayout" name="horizontalLayout_9">
+             <property name="spacing">
+              <number>0</number>
+             </property>
+             <item>
+              <widget class="QPushButton" name="cmdUIDensityUp">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-add">
+                 <normaloff>../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../.designer/backup</iconset>
+               </property>
+               <property name="flat">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QSpinBox" name="spinUIDensity">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="alignment">
+                <set>Qt::AlignCenter</set>
+               </property>
+               <property name="buttonSymbols">
+                <enum>QAbstractSpinBox::NoButtons</enum>
+               </property>
+               <property name="accelerated">
+                <bool>true</bool>
+               </property>
+               <property name="minimum">
+                <number>-20</number>
+               </property>
+               <property name="maximum">
+                <number>20</number>
+               </property>
+               <property name="value">
+                <number>0</number>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QPushButton" name="cmdUIDensityDown">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Fixed" vsizetype="Maximum">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string/>
+               </property>
+               <property name="icon">
+                <iconset theme="list-remove">
+                 <normaloff>../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../.designer/backup</iconset>
+               </property>
+               <property name="flat">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="1" column="0" colspan="2">
+            <widget class="QLabel" name="label_19">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string>Language</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0">
+            <widget class="QLabel" name="labelThemeError">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string/>
+             </property>
+             <property name="textInteractionFlags">
+              <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+             </property>
+            </widget>
+           </item>
+           <item row="5" column="2" colspan="2">
+            <widget class="QComboBox" name="comboUIQtPlatform">
+             <property name="editable">
+              <bool>true</bool>
+             </property>
+             <item>
+              <property name="text">
+               <string/>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">xcb</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">wayland</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="5" column="0" colspan="2">
+            <widget class="QLabel" name="label_29">
+             <property name="toolTip">
+              <string>This option will set QT_QPA_PLATFORM when launching the GUI.
+
+xcb         - X11 compatibility. If you experience issues with wayland, use this plugin.
+wayland</string>
+             </property>
+             <property name="text">
+              <string>Qt platform plugin</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="page_6">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>259</width>
+            <height>178</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>Server</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_13">
+           <item row="0" column="1">
+            <widget class="QComboBox" name="comboGrpcMsgSize">
+             <property name="editable">
+              <bool>true</bool>
+             </property>
+             <property name="currentText">
+              <string notr="true">4MiB</string>
+             </property>
+             <item>
+              <property name="text">
+               <string>4MiB</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>8MiB</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>16MiB</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>32MiB</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_24">
+             <property name="toolTip">
+              <string>&lt;p&gt;Simple: no authentication&lt;/p&gt;
+&lt;p&gt;TLS simple/mutual: use SSL certificates to authenticate nodes.&lt;/p&gt;
+&lt;p&gt;Visit the wiki for more information.&lt;/p&gt;</string>
+             </property>
+             <property name="text">
+              <string>Authentication type</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0" colspan="2">
+            <widget class="QLineEdit" name="lineCertFile">
+             <property name="placeholderText">
+              <string>Absolute path to the cert file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0" colspan="2">
+            <widget class="QLineEdit" name="lineCertKeyFile">
+             <property name="placeholderText">
+              <string>Absolute path to the cert key file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QComboBox" name="comboAuthType">
+             <item>
+              <property name="text">
+               <string>Simple</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Simple TLS</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Mutual TLS</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="2" column="0" colspan="2">
+            <widget class="QLineEdit" name="lineCACertFile">
+             <property name="placeholderText">
+              <string>Absolute path to the CA cert file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_20">
+             <property name="toolTip">
+              <string>Maximum size of each message from nodes. Default 4MB</string>
+             </property>
+             <property name="text">
+              <string>Max gRPC channel size</string>
+             </property>
+            </widget>
+           </item>
+           <item row="5" column="0" colspan="2">
+            <widget class="QLabel" name="label_28">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="text">
+              <string>&lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Nodes-authentication#nodes-authentication-added-in-v161&quot;&gt;More information&lt;/a&gt;</string>
+             </property>
+             <property name="openExternalLinks">
+              <bool>true</bool>
+             </property>
+             <property name="textInteractionFlags">
+              <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="page_3">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>321</width>
+            <height>112</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>Desktop notifications</string>
+          </attribute>
+          <layout class="QFormLayout" name="formLayout">
+           <item row="0" column="0" colspan="2">
+            <widget class="QGroupBox" name="groupNotifs">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="title">
+              <string>Enable</string>
+             </property>
+             <property name="flat">
+              <bool>true</bool>
+             </property>
+             <property name="checkable">
+              <bool>true</bool>
+             </property>
+             <layout class="QGridLayout" name="gridLayout_9">
+              <item row="0" column="0">
+               <widget class="QRadioButton" name="radioSysNotifs">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Use system notifications</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="1" column="0">
+               <widget class="QRadioButton" name="radioQtNotifs">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Use Qt notifications</string>
+                </property>
+               </widget>
+              </item>
+              <item row="0" column="1">
+               <layout class="QHBoxLayout" name="horizontalLayout_3">
+                <item>
+                 <widget class="QLabel" name="labelNotifsWarning">
+                  <property name="text">
+                   <string/>
+                  </property>
+                 </widget>
+                </item>
+                <item>
+                 <spacer name="horizontalSpacer_5">
+                  <property name="orientation">
+                   <enum>Qt::Horizontal</enum>
+                  </property>
+                  <property name="sizeHint" stdset="0">
+                   <size>
+                    <width>20</width>
+                    <height>20</height>
+                   </size>
+                  </property>
+                 </spacer>
+                </item>
+                <item>
+                 <widget class="QPushButton" name="cmdTestNotifs">
+                  <property name="text">
+                   <string>Test</string>
+                  </property>
+                 </widget>
+                </item>
+               </layout>
+              </item>
+             </layout>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="page_4">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>586</width>
+            <height>301</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>Events tab columns</string>
+          </attribute>
+          <layout class="QFormLayout" name="formLayout_2">
+           <item row="0" column="0" colspan="2">
+            <widget class="QGroupBox" name="groupBox_2">
+             <property name="sizePolicy">
+              <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+               <horstretch>0</horstretch>
+               <verstretch>0</verstretch>
+              </sizepolicy>
+             </property>
+             <property name="alignment">
+              <set>Qt::AlignCenter</set>
+             </property>
+             <layout class="QGridLayout" name="gridLayout_7">
+              <property name="leftMargin">
+               <number>4</number>
+              </property>
+              <property name="topMargin">
+               <number>0</number>
+              </property>
+              <property name="rightMargin">
+               <number>4</number>
+              </property>
+              <property name="bottomMargin">
+               <number>4</number>
+              </property>
+              <property name="spacing">
+               <number>0</number>
+              </property>
+              <item row="2" column="1">
+               <widget class="QCheckBox" name="checkHideNode">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Node</string>
+                </property>
+                <property name="checked">
+                 <bool>false</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="8" column="1">
+               <widget class="QCheckBox" name="checkHideCmdline">
+                <property name="text">
+                 <string>Command line</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="2" column="0">
+               <widget class="QCheckBox" name="checkHideTime">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Time</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="2" column="2">
+               <widget class="QCheckBox" name="checkHideAction">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Action</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="4" column="0">
+               <widget class="QCheckBox" name="checkHideSrcPort">
+                <property name="text">
+                 <string>Source port</string>
+                </property>
+               </widget>
+              </item>
+              <item row="4" column="1">
+               <widget class="QCheckBox" name="checkHideSrcIP">
+                <property name="text">
+                 <string>Source IP</string>
+                </property>
+               </widget>
+              </item>
+              <item row="8" column="2">
+               <widget class="QCheckBox" name="checkHideRule">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Rule</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="8" column="0">
+               <widget class="QCheckBox" name="checkHideProc">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Process</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="5" column="1">
+               <widget class="QCheckBox" name="checkHideDstIP">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Dest IP</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="5" column="0">
+               <widget class="QCheckBox" name="checkHideDstPort">
+                <property name="text">
+                 <string>Dest port</string>
+                </property>
+               </widget>
+              </item>
+              <item row="5" column="2">
+               <widget class="QCheckBox" name="checkHideDstHost">
+                <property name="text">
+                 <string>Dest host</string>
+                </property>
+               </widget>
+              </item>
+              <item row="7" column="0">
+               <widget class="QCheckBox" name="checkHideProto">
+                <property name="sizePolicy">
+                 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
+                  <horstretch>0</horstretch>
+                  <verstretch>0</verstretch>
+                 </sizepolicy>
+                </property>
+                <property name="text">
+                 <string>Protocol</string>
+                </property>
+                <property name="checked">
+                 <bool>true</bool>
+                </property>
+               </widget>
+              </item>
+              <item row="7" column="1">
+               <widget class="QCheckBox" name="checkHidePID">
+                <property name="text">
+                 <string>PID</string>
+                </property>
+               </widget>
+              </item>
+              <item row="7" column="2">
+               <widget class="QCheckBox" name="checkHideUID">
+                <property name="text">
+                 <string>UID</string>
+                </property>
+               </widget>
+              </item>
+             </layout>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_5">
+      <attribute name="title">
+       <string>Rules</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_11">
+       <item row="0" column="0">
+        <layout class="QHBoxLayout" name="horizontalLayout_5">
+         <item>
+          <widget class="QCheckBox" name="checkUIRules">
+           <property name="toolTip">
+            <string>When this option is selected, the rules of the selected duration won't be added to the list of temporary rules in the GUI.
+
+Temporary rules will still be valid, and you can use them when prompted to allow/deny a new connection.</string>
+           </property>
+           <property name="text">
+            <string>Don't save/Delete rules of duration</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QComboBox" name="comboUIRules">
+           <item>
+            <property name="text">
+             <string>any temporary rules</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>once</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>30s or less</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>5m or less</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>15m or less</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>30m or less</string>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>1h or less</string>
+            </property>
+           </item>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="1" column="0">
+        <spacer name="verticalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_3">
+      <attribute name="title">
+       <string>Nodes</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_4" rowstretch="0,0,0,0">
+       <item row="0" column="2">
+        <widget class="QCheckBox" name="checkApplyToNodes">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string>Apply configuration to all nodes</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QLabel" name="label_9">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Ignored">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string>Version</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="0">
+        <widget class="QComboBox" name="comboNodes">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="2">
+        <widget class="QLabel" name="labelNodeVersion">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="1">
+        <spacer name="horizontalSpacer_3">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Preferred</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="1" column="2">
+        <widget class="QLabel" name="labelNodeName">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="0" colspan="3">
+        <widget class="QToolBox" name="toolBox">
+         <property name="currentIndex">
+          <number>0</number>
+         </property>
+         <widget class="QWidget" name="page">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>586</width>
+            <height>260</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>General</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_10">
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_10">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default action will be applied to new outbound connections in two scenarios:&lt;/p&gt;&lt;p&gt;when the daemon is not connected to the UI, or when there's a pop-up running.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Default action when the GUI is disconnected</string>
+             </property>
+             <property name="wordWrap">
+              <bool>true</bool>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="label_11">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The default duration will take place when there's no UI connected.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Default duration</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="1">
+            <widget class="QComboBox" name="comboNodeDuration">
+             <item>
+              <property name="text">
+               <string>once</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>until restart</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>always</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QComboBox" name="comboNodeAction">
+             <property name="editable">
+              <bool>false</bool>
+             </property>
+             <item>
+              <property name="text">
+               <string>deny</string>
+              </property>
+              <property name="icon">
+               <iconset theme="emblem-important">
+                <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>allow</string>
+              </property>
+              <property name="icon">
+               <iconset theme="emblem-default">
+                <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>reject</string>
+              </property>
+              <property name="icon">
+               <iconset theme="window-close">
+                <normaloff>.</normaloff>.</iconset>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="4" column="1">
+            <widget class="QComboBox" name="comboNodeMonitorMethod">
+             <property name="accessibleDescription">
+              <string notr="true"/>
+             </property>
+             <item>
+              <property name="text">
+               <string notr="true">proc</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">ebpf</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">audit</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="label_12">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will prompt you to allow or deny connections that don't have an associated PID, due to several reasons, mostly due to bad state connections.&lt;/p&gt;&lt;p&gt;The pop-up dialog will only contain information about the network connection.&lt;/p&gt;&lt;p&gt;There're some scenarios where these are valid connections though, like when establishing a VPN using WireGuard.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Debug invalid connections</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0">
+            <widget class="QLabel" name="label_13">
+             <property name="text">
+              <string>Process monitor method</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_15">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Address of the node.&lt;/p&gt;&lt;p&gt;Default: unix:///tmp/osui.sock (unix:// is mandatory if it's a Unix socket)&lt;/p&gt;&lt;p&gt;It can also be an IP address with the port: 127.0.0.1:50051&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Address</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QCheckBox" name="checkInterceptUnknown">
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QComboBox" name="comboNodeAddress">
+             <property name="editable">
+              <bool>true</bool>
+             </property>
+             <item>
+              <property name="text">
+               <string>unix:///tmp/osui.sock</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="page_2">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>376</width>
+            <height>118</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>Logging</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_12">
+           <item row="2" column="1">
+            <widget class="QCheckBox" name="checkNodeLogUTC">
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="0">
+            <widget class="QLabel" name="label_7">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Log file to write logs.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;/dev/stdout will print logs to the standard output.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Log file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLabel" name="label_23">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will log timestamp microseconds.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Log timestamp microseconds</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="1">
+            <widget class="QComboBox" name="comboNodeLogLevel">
+             <property name="accessibleDescription">
+              <string notr="true"/>
+             </property>
+             <item>
+              <property name="text">
+               <string notr="true">DEBUG</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">INFO</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">IMPORTANT</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">WARNING</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">ERROR</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string notr="true">FATAL</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <widget class="QLabel" name="label_22">
+             <property name="toolTip">
+              <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;If checked, OpenSnitch will use the UTC timezone for timestamps.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+             </property>
+             <property name="text">
+              <string>Log UTC timestamps</string>
+             </property>
+            </widget>
+           </item>
+           <item row="1" column="0">
+            <widget class="QLabel" name="label_14">
+             <property name="text">
+              <string>Default log level</string>
+             </property>
+            </widget>
+           </item>
+           <item row="0" column="1">
+            <widget class="QComboBox" name="comboNodeLogFile">
+             <property name="editable">
+              <bool>true</bool>
+             </property>
+             <item>
+              <property name="text">
+               <string>/var/log/opensnitchd.log</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>/dev/stdout</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item row="3" column="1">
+            <widget class="QCheckBox" name="checkNodeLogMicro">
+             <property name="text">
+              <string/>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="1">
+            <spacer name="verticalSpacer_3">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+             <property name="sizeType">
+              <enum>QSizePolicy::Maximum</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>20</width>
+               <height>40</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+         <widget class="QWidget" name="page_7">
+          <property name="geometry">
+           <rect>
+            <x>0</x>
+            <y>0</y>
+            <width>296</width>
+            <height>211</height>
+           </rect>
+          </property>
+          <attribute name="label">
+           <string>Authentication</string>
+          </attribute>
+          <layout class="QGridLayout" name="gridLayout_15">
+           <item row="5" column="0">
+            <widget class="QLineEdit" name="lineNodeCertFile">
+             <property name="placeholderText">
+              <string>Absolute path to the cert file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="2" column="0">
+            <layout class="QHBoxLayout" name="horizontalLayout_7">
+             <item>
+              <widget class="QLabel" name="label_25">
+               <property name="toolTip">
+                <string>&lt;p&gt;Simple: no authentication, TLS simple/mutual: use SSL certificates to authenticate nodes.&lt;/p&gt;&lt;p&gt;Visit the wiki for more information.&lt;/p&gt;</string>
+               </property>
+               <property name="text">
+                <string>Authentication type</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QComboBox" name="comboNodeAuthType">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <item>
+                <property name="text">
+                 <string>Simple</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>Simple TLS</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>Mutual TLS</string>
+                </property>
+               </item>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="3" column="0">
+            <widget class="QLineEdit" name="lineNodeCACertFile">
+             <property name="placeholderText">
+              <string>Absolute path to the CA cert file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="8" column="0">
+            <layout class="QHBoxLayout" name="horizontalLayout_6">
+             <item>
+              <widget class="QCheckBox" name="checkNodeAuthSkipVerify">
+               <property name="text">
+                <string>Don't verify certs</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QComboBox" name="comboNodeAuthVerifyType">
+               <item>
+                <property name="text">
+                 <string>no-client-cert</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>req-cert</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>req-any-cert</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>verify-cert</string>
+                </property>
+               </item>
+               <item>
+                <property name="text">
+                 <string>req-and-verify-cert</string>
+                </property>
+               </item>
+              </widget>
+             </item>
+            </layout>
+           </item>
+           <item row="6" column="0">
+            <widget class="QLineEdit" name="lineNodeCertKeyFile">
+             <property name="placeholderText">
+              <string>Absolute path to the cert key file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="4" column="0">
+            <widget class="QLineEdit" name="lineNodeServerCertFile">
+             <property name="placeholderText">
+              <string>Absolute path to the server cert file</string>
+             </property>
+            </widget>
+           </item>
+           <item row="9" column="0">
+            <widget class="QLabel" name="label_26">
+             <property name="text">
+              <string>&lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Nodes-authentication#nodes-authentication-added-in-v161&quot;&gt;More information&lt;/a&gt;</string>
+             </property>
+             <property name="openExternalLinks">
+              <bool>true</bool>
+             </property>
+             <property name="textInteractionFlags">
+              <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QLabel" name="label_8">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string>HostName</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="title">
+       <string>Database</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0">
+       <property name="sizeConstraint">
+        <enum>QLayout::SetDefaultConstraint</enum>
+       </property>
+       <property name="leftMargin">
+        <number>9</number>
+       </property>
+       <property name="verticalSpacing">
+        <number>15</number>
+       </property>
+       <item row="1" column="1" colspan="3">
+        <widget class="QLabel" name="dbLabel">
+         <property name="text">
+          <string/>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="3">
+        <widget class="QComboBox" name="comboDBType">
+         <property name="enabled">
+          <bool>true</bool>
+         </property>
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <item>
+          <property name="text">
+           <string>In memory</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>File</string>
+          </property>
+         </item>
+        </widget>
+       </item>
+       <item row="0" column="0">
+        <widget class="QLabel" name="label_6">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string>Database type</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QPushButton" name="dbFileButton">
+         <property name="text">
+          <string>Select</string>
+         </property>
+         <property name="icon">
+          <iconset theme="document-open">
+           <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="0">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="0" column="1">
+        <spacer name="horizontalSpacer_2">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>40</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="2" column="0" colspan="4">
+        <layout class="QGridLayout" name="gridLayout_8">
+         <property name="verticalSpacing">
+          <number>10</number>
+         </property>
+         <item row="0" column="3">
+          <widget class="QSpinBox" name="spinDBMaxDays">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+           <property name="buttonSymbols">
+            <enum>QAbstractSpinBox::NoButtons</enum>
+           </property>
+           <property name="minimum">
+            <number>1</number>
+           </property>
+           <property name="maximum">
+            <number>99999</number>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="2">
+          <widget class="QPushButton" name="cmdDBMaxDaysUp">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="list-add">
+             <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="5">
+          <widget class="QLabel" name="labelDBPurgeMinutes">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string>minutes</string>
+           </property>
+           <property name="margin">
+            <number>5</number>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="3">
+          <widget class="QSpinBox" name="spinDBPurgeInterval">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignCenter</set>
+           </property>
+           <property name="buttonSymbols">
+            <enum>QAbstractSpinBox::NoButtons</enum>
+           </property>
+           <property name="minimum">
+            <number>5</number>
+           </property>
+           <property name="maximum">
+            <number>1440</number>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="0">
+          <widget class="QLabel" name="labelDBPurgeInterval">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string>Minutes between events purges</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="1">
+          <spacer name="horizontalSpacer">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item row="0" column="5">
+          <widget class="QLabel" name="labelDBPurgeDays">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string>days</string>
+           </property>
+           <property name="margin">
+            <number>5</number>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="0">
+          <widget class="QCheckBox" name="checkDBMaxDays">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string>Maximum days of events to keep</string>
+           </property>
+          </widget>
+         </item>
+         <item row="0" column="4">
+          <widget class="QPushButton" name="cmdDBMaxDaysDown">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="list-remove">
+             <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="2">
+          <widget class="QPushButton" name="cmdDBPurgesUp">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="list-add">
+             <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item row="1" column="4">
+          <widget class="QPushButton" name="cmdDBPurgesDown">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="list-remove">
+             <normaloff>../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item row="2" column="0">
+          <widget class="QCheckBox" name="checkDBJrnlWal">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string>Enable DB Write-Ahead Logging (WAL)</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QLabel" name="statusLabel">
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/res/process_details.ui b/ui/opensnitch/res/process_details.ui
new file mode 100644 (file)
index 0000000..5c34de5
--- /dev/null
@@ -0,0 +1,269 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ProcessDetailsDialog</class>
+ <widget class="QDialog" name="ProcessDetailsDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>731</width>
+    <height>478</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Process details</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_2">
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout">
+       <item>
+        <widget class="QLabel" name="labelProcIcon">
+         <property name="minimumSize">
+          <size>
+           <width>48</width>
+           <height>48</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>64</width>
+           <height>64</height>
+          </size>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <item>
+          <widget class="QLabel" name="labelProcName">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="text">
+            <string>loading...</string>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+           </property>
+           <property name="wordWrap">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="labelProcArgs">
+           <property name="text">
+            <string>loading...</string>
+           </property>
+           <property name="textFormat">
+            <enum>Qt::PlainText</enum>
+           </property>
+           <property name="alignment">
+            <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+           </property>
+           <property name="wordWrap">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <widget class="QLabel" name="labelCwd">
+       <property name="text">
+        <string>CWD: loading...</string>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <layout class="QHBoxLayout" name="horizontalLayout_3">
+       <item>
+        <widget class="QLabel" name="labelStatm">
+         <property name="text">
+          <string>mem stats: loading...</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="tabPosition">
+      <enum>QTabWidget::South</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="title">
+       <string>Status</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_3">
+       <item row="0" column="1">
+        <widget class="QPlainTextEdit" name="textStatus">
+         <property name="undoRedoEnabled">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_3">
+      <attribute name="title">
+       <string>Open files</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_4">
+       <item row="1" column="0">
+        <widget class="QPlainTextEdit" name="textOpenedFiles">
+         <property name="undoRedoEnabled">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tabWidgetPage1">
+      <attribute name="title">
+       <string>I/O Statistics</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout">
+       <item row="1" column="0">
+        <widget class="QPlainTextEdit" name="textIOStats">
+         <property name="undoRedoEnabled">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tabWidgetPage2">
+      <attribute name="title">
+       <string>Memory mapped files</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="1" column="0">
+        <widget class="QPlainTextEdit" name="textMappedFiles">
+         <property name="undoRedoEnabled">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>Stack</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_5">
+       <item row="0" column="0">
+        <widget class="QPlainTextEdit" name="textStack">
+         <property name="undoRedoEnabled">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_4">
+      <attribute name="title">
+       <string>Environment variables</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_6">
+       <item row="0" column="0">
+        <widget class="QPlainTextEdit" name="textEnv">
+         <property name="undoRedoEnabled">
+          <bool>false</bool>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="label">
+       <property name="text">
+        <string>Application pids</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="comboPids">
+       <property name="maxVisibleItems">
+        <number>100</number>
+       </property>
+       <property name="sizeAdjustPolicy">
+        <enum>QComboBox::AdjustToContents</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_3">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdAction">
+       <property name="toolTip">
+        <string>Start or stop monitoring this process</string>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+       <property name="icon">
+        <iconset theme="media-playback-start"/>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="cmdClose">
+       <property name="text">
+        <string>Close</string>
+       </property>
+       <property name="icon">
+        <iconset theme="window-close"/>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/res/prompt.ui b/ui/opensnitch/res/prompt.ui
new file mode 100644 (file)
index 0000000..2a132c0
--- /dev/null
@@ -0,0 +1,914 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>515</width>
+    <height>315</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>0</width>
+    <height>200</height>
+   </size>
+  </property>
+  <property name="font">
+   <font>
+    <kerning>true</kerning>
+   </font>
+  </property>
+  <property name="windowTitle">
+   <string>opensnitch-qt</string>
+  </property>
+  <property name="windowIcon">
+   <iconset resource="resources.qrc">
+    <normaloff>:/pics/icon-white.png</normaloff>
+    <normalon>:/pics/icon.png</normalon>:/pics/icon-white.png</iconset>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>false</bool>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,0,0,0,1">
+   <property name="spacing">
+    <number>2</number>
+   </property>
+   <property name="sizeConstraint">
+    <enum>QLayout::SetMinAndMaxSize</enum>
+   </property>
+   <property name="leftMargin">
+    <number>5</number>
+   </property>
+   <property name="topMargin">
+    <number>5</number>
+   </property>
+   <property name="rightMargin">
+    <number>5</number>
+   </property>
+   <property name="bottomMargin">
+    <number>5</number>
+   </property>
+   <item>
+    <layout class="QFormLayout" name="formLayout">
+     <property name="fieldGrowthPolicy">
+      <enum>QFormLayout::ExpandingFieldsGrow</enum>
+     </property>
+     <property name="labelAlignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+     <property name="verticalSpacing">
+      <number>0</number>
+     </property>
+     <item row="0" column="0">
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <item>
+        <widget class="QLabel" name="iconLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="minimumSize">
+          <size>
+           <width>64</width>
+           <height>64</height>
+          </size>
+         </property>
+         <property name="maximumSize">
+          <size>
+           <width>64</width>
+           <height>64</height>
+          </size>
+         </property>
+         <property name="text">
+          <string/>
+         </property>
+         <property name="pixmap">
+          <pixmap resource="resources.qrc">:/pics/icon.png</pixmap>
+         </property>
+         <property name="scaledContents">
+          <bool>true</bool>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_4">
+         <property name="text">
+          <string/>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item row="0" column="1">
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <property name="spacing">
+        <number>0</number>
+       </property>
+       <item>
+        <widget class="QLabel" name="appNameLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>16</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string notr="true">Chromium Web Browser</string>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::PlainText</enum>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="appDescriptionLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>10</pointsize>
+           <weight>50</weight>
+           <italic>true</italic>
+           <bold>false</bold>
+           <underline>true</underline>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string notr="true">&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;/opt/google/chrome/bin/chrome --something abc --more-long  def --for-word-wrapping&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="appPathLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string notr="true">(/path/to/bin/chromium)</string>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::PlainText</enum>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="argsLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <family>DejaVu Sans</family>
+           <pointsize>9</pointsize>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string notr="true">(/path/to/bin/chromium)</string>
+         </property>
+         <property name="textFormat">
+          <enum>Qt::PlainText</enum>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+         </property>
+         <property name="wordWrap">
+          <bool>true</bool>
+         </property>
+         <property name="textInteractionFlags">
+          <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="Line" name="line">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QVBoxLayout" name="verticalLayout_5">
+     <property name="spacing">
+      <number>0</number>
+     </property>
+     <item>
+      <widget class="QLabel" name="messageLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string notr="true">Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+       </property>
+       <property name="wordWrap">
+        <bool>true</bool>
+       </property>
+       <property name="margin">
+        <number>2</number>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QGridLayout" name="gridLayout">
+     <property name="topMargin">
+      <number>6</number>
+     </property>
+     <property name="verticalSpacing">
+      <number>3</number>
+     </property>
+     <item row="1" column="1">
+      <widget class="QLabel" name="label">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>Source IP</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="3">
+      <widget class="QLabel" name="cwdLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+     <item row="7" column="3">
+      <widget class="QLabel" name="pidLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+     <item row="6" column="1">
+      <widget class="QLabel" name="label_6">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>User ID</string>
+       </property>
+      </widget>
+     </item>
+     <item row="6" column="0">
+      <widget class="QCheckBox" name="checkUserID">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item row="7" column="1">
+      <widget class="QLabel" name="pidLabelWidget">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>Process ID</string>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="1">
+      <widget class="QLabel" name="destPortLabel_1">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>Dst Port</string>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="1">
+      <widget class="QLabel" name="label_3">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>Destination IP</string>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="1">
+      <widget class="QLabel" name="label_2">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Executed from&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+       </property>
+      </widget>
+     </item>
+     <item row="6" column="3">
+      <widget class="QLabel" name="uidLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="3">
+      <widget class="QLabel" name="destIPLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+     <item row="0" column="2">
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item row="2" column="0">
+      <widget class="QCheckBox" name="checkDstIP">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item row="2" column="2">
+      <widget class="QComboBox" name="whatIPCombo">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumContentsLength">
+        <number>0</number>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="0">
+      <widget class="QCheckBox" name="checkDstPort">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="text">
+        <string/>
+       </property>
+      </widget>
+     </item>
+     <item row="5" column="3">
+      <widget class="QLabel" name="destPortLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+     <item row="1" column="3">
+      <widget class="QLabel" name="sourceIPLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="frameShape">
+        <enum>QFrame::NoFrame</enum>
+       </property>
+       <property name="text">
+        <string>TextLabel</string>
+       </property>
+       <property name="textFormat">
+        <enum>Qt::PlainText</enum>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+       </property>
+       <property name="textInteractionFlags">
+        <set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_5">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="minimumSize">
+      <size>
+       <width>0</width>
+       <height>0</height>
+      </size>
+     </property>
+     <property name="text">
+      <string notr="true"/>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout" stretch="3,2,1,0,0">
+     <property name="sizeConstraint">
+      <enum>QLayout::SetMinimumSize</enum>
+     </property>
+     <item>
+      <widget class="QComboBox" name="whatCombo">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>97</width>
+         <height>26</height>
+        </size>
+       </property>
+       <item>
+        <property name="text">
+         <string>from this executable</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>from this command line</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>this destination port</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>this user</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>this destination ip</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>from this PID</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="durationCombo">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>97</width>
+         <height>26</height>
+        </size>
+       </property>
+       <property name="currentText">
+        <string>once</string>
+       </property>
+       <item>
+        <property name="text">
+         <string>once</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>30s</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>5m</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>15m</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>30m</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>1h</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>until reboot</string>
+        </property>
+       </item>
+       <item>
+        <property name="text">
+         <string>forever</string>
+        </property>
+       </item>
+      </widget>
+     </item>
+     <item>
+      <widget class="QToolButton" name="actionButton">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>97</width>
+         <height>26</height>
+        </size>
+       </property>
+       <property name="styleSheet">
+        <string notr="true"/>
+       </property>
+       <property name="text">
+        <string>action</string>
+       </property>
+       <property name="icon">
+        <iconset theme="emblem-important">
+         <normaloff>.</normaloff>.</iconset>
+       </property>
+       <property name="popupMode">
+        <enum>QToolButton::MenuButtonPopup</enum>
+       </property>
+       <property name="toolButtonStyle">
+        <enum>Qt::ToolButtonTextBesideIcon</enum>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="allowButton">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Maximum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>97</width>
+         <height>26</height>
+        </size>
+       </property>
+       <property name="text">
+        <string>Allow</string>
+       </property>
+       <property name="icon">
+        <iconset theme="emblem-default">
+         <normaloff>.</normaloff>.</iconset>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="checkAdvanced">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="minimumSize">
+        <size>
+         <width>26</width>
+         <height>26</height>
+        </size>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>40</width>
+         <height>30</height>
+        </size>
+       </property>
+       <property name="text">
+        <string>+</string>
+       </property>
+       <property name="checkable">
+        <bool>true</bool>
+       </property>
+       <property name="checked">
+        <bool>false</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <tabstops>
+  <tabstop>checkAdvanced</tabstop>
+  <tabstop>allowButton</tabstop>
+  <tabstop>actionButton</tabstop>
+  <tabstop>durationCombo</tabstop>
+  <tabstop>whatCombo</tabstop>
+  <tabstop>checkDstPort</tabstop>
+  <tabstop>checkDstIP</tabstop>
+  <tabstop>checkUserID</tabstop>
+  <tabstop>whatIPCombo</tabstop>
+ </tabstops>
+ <resources>
+  <include location="resources.qrc"/>
+  <include location="../../../../../../../../.designer/backup/resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/res/resources.qrc b/ui/opensnitch/res/resources.qrc
new file mode 100644 (file)
index 0000000..35bde88
--- /dev/null
@@ -0,0 +1,8 @@
+<RCC>
+  <qresource prefix="pics">
+    <file>icon-white.svg</file>
+    <file>icon-white.png</file>
+    <file>icon-red.png</file>
+    <file>icon.png</file>
+  </qresource>
+</RCC>
diff --git a/ui/opensnitch/res/ruleseditor.ui b/ui/opensnitch/res/ruleseditor.ui
new file mode 100644 (file)
index 0000000..3100376
--- /dev/null
@@ -0,0 +1,1153 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>RulesDialog</class>
+ <widget class="QDialog" name="RulesDialog">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>552</width>
+    <height>559</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Rule</string>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_4">
+   <item row="6" column="0">
+    <widget class="QGroupBox" name="enableGroup">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="flat">
+      <bool>false</bool>
+     </property>
+     <property name="checkable">
+      <bool>false</bool>
+     </property>
+     <property name="checked">
+      <bool>false</bool>
+     </property>
+     <layout class="QGridLayout" name="gridLayout" columnstretch="0,0,0,0,0,0,0">
+      <property name="sizeConstraint">
+       <enum>QLayout::SetDefaultConstraint</enum>
+      </property>
+      <property name="topMargin">
+       <number>4</number>
+      </property>
+      <property name="bottomMargin">
+       <number>4</number>
+      </property>
+      <property name="verticalSpacing">
+       <number>12</number>
+      </property>
+      <item row="3" column="1">
+       <widget class="QLabel" name="label_2">
+        <property name="text">
+         <string>Action</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="4">
+       <spacer name="horizontalSpacer_5">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item row="3" column="4">
+       <spacer name="horizontalSpacer_4">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>40</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item row="4" column="1">
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Duration</string>
+        </property>
+       </widget>
+      </item>
+      <item row="4" column="6">
+       <widget class="QComboBox" name="durationCombo">
+        <item>
+         <property name="text">
+          <string>once</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string notr="true">30s</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string notr="true">5m</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string notr="true">15m</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string notr="true">30m</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string notr="true">1h</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>until reboot</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>always</string>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item row="3" column="6">
+       <layout class="QHBoxLayout" name="horizontalLayout_2">
+        <item>
+         <widget class="QRadioButton" name="actionDenyRadio">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="toolTip">
+           <string>Deny will just discard the connection</string>
+          </property>
+          <property name="text">
+           <string>Deny</string>
+          </property>
+          <property name="icon">
+           <iconset theme="emblem-important">
+            <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+          </property>
+          <property name="checked">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QRadioButton" name="actionRejectRadio">
+          <property name="toolTip">
+           <string>Reject will drop the connection, and kill the socket that initiated it</string>
+          </property>
+          <property name="text">
+           <string>Reject</string>
+          </property>
+          <property name="icon">
+           <iconset theme="window-close">
+            <normaloff>.</normaloff>.</iconset>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QRadioButton" name="actionAllowRadio">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="toolTip">
+           <string>Allow will allow the connection</string>
+          </property>
+          <property name="layoutDirection">
+           <enum>Qt::LeftToRight</enum>
+          </property>
+          <property name="text">
+           <string>Allow</string>
+          </property>
+          <property name="icon">
+           <iconset theme="emblem-default">
+            <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="4" column="0">
+    <widget class="QCheckBox" name="enableCheck">
+     <property name="text">
+      <string>Enable</string>
+     </property>
+    </widget>
+   </item>
+   <item row="5" column="0">
+    <widget class="QCheckBox" name="precedenceCheck">
+     <property name="toolTip">
+      <string>If checked, this rule will take precedence over the rest of the rules. No others rules will be checked after this one.
+
+You must name the rule in such manner that it'll be checked first, because they're checked in alphabetical order. For example:
+
+[x] Priority - 000-priority-rule
+[  ] Priority - 001-less-priority-rule</string>
+     </property>
+     <property name="text">
+      <string>Priority rule</string>
+     </property>
+    </widget>
+   </item>
+   <item row="0" column="0">
+    <widget class="QLineEdit" name="ruleNameEdit">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Maximum">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="toolTip">
+      <string>The rules are checked in alphabetical order, so you can name them accordingly to prioritize them.
+
+000-allow-localhost
+001-deny-broadcast
+...</string>
+     </property>
+     <property name="placeholderText">
+      <string>Name</string>
+     </property>
+     <property name="clearButtonEnabled">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="8" column="0">
+    <widget class="QLabel" name="statusLabel">
+     <property name="enabled">
+      <bool>true</bool>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item row="10" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_3">
+     <item>
+      <spacer name="horizontalSpacer_6">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QDialogButtonBox" name="buttonBox">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="standardButtons">
+        <set>QDialogButtonBox::Apply|QDialogButtonBox::Close|QDialogButtonBox::Help|QDialogButtonBox::Reset</set>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="2" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="label_3">
+       <property name="text">
+        <string>Node</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="nodesCombo"/>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QCheckBox" name="nodeApplyAllCheck">
+       <property name="text">
+        <string>Apply rule to all nodes</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item row="7" column="0">
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="elideMode">
+      <enum>Qt::ElideRight</enum>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="tabWidgetPage1">
+      <attribute name="icon">
+       <iconset theme="system-run">
+        <normaloff>.</normaloff>.</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Applications</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="0" column="1" colspan="2">
+        <widget class="QLineEdit" name="procLine">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;The value of this field is always the absolute path to the executable: /path/to/binary&lt;br/&gt;&lt;/p&gt;&lt;p&gt;Examples:&lt;/p&gt;&lt;p&gt;- Simple: /path/to/binary&lt;/p&gt;&lt;p&gt;- Multiple paths: ^/usr/lib(64|)/firefox/firefox$&lt;/p&gt;&lt;p&gt;- Multiple binaries: ^(/usr/sbin/ntpd|/lib/systemd/systemd-timesyncd|/usr/bin/xbrlapi|/usr/bin/dirmngr)$ &lt;/p&gt;&lt;p&gt;- Deny/Allow executions from /tmp:&lt;/p&gt;&lt;p&gt;^/(var/|)tmp/.*$&lt;br/&gt;&lt;/p&gt;&lt;p&gt;For more examples visit the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/wiki/Rules-examples&quot;&gt;wiki page&lt;/a&gt; or ask on the &lt;a href=&quot;https://github.com/evilsocket/opensnitch/discussions&quot;&gt;Discussion forums&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="placeholderText">
+          <string notr="true">/path/to/executable, .*/bin/executable[0-9\.]+$, ...</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="1">
+        <widget class="QCheckBox" name="checkCmdlineRegexp">
+         <property name="text">
+          <string>Is regular expression</string>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="0">
+        <widget class="QCheckBox" name="uidCheck">
+         <property name="text">
+          <string>From this user ID</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QCheckBox" name="cmdlineCheck">
+         <property name="text">
+          <string>From this command line</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1" colspan="2">
+        <widget class="QLineEdit" name="cmdlineLine">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This field will contain and match the command line that was executed by the user.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the command, only the command will appear:&lt;/p&gt;&lt;p&gt;telnet 1.2.3.4&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If the user typed the absolute or relative path to the command, that is what will appear:&lt;/p&gt;&lt;p&gt;/usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;p&gt;../../../usr/bin/telnet 1.2.3.4&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="placeholderText">
+          <string notr="true">curl -L https://www.domain.com</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="0">
+        <widget class="QCheckBox" name="pidCheck">
+         <property name="text">
+          <string>From this PID</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_11">
+         <item>
+          <spacer name="horizontalSpacer_11">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="pidLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="4" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_8">
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeType">
+            <enum>QSizePolicy::MinimumExpanding</enum>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QComboBox" name="uidCombo">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="editable">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="0" column="0">
+        <widget class="QCheckBox" name="procCheck">
+         <property name="text">
+          <string>From this executable</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <widget class="QCheckBox" name="checkProcRegexp">
+         <property name="text">
+          <string>is regular expression</string>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tabWidgetPage2">
+      <attribute name="icon">
+       <iconset theme="preferences-system-network">
+        <normaloff>.</normaloff>.</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Network</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_3">
+       <item row="6" column="1">
+        <spacer name="horizontalSpacer_9">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Maximum</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>60</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="7" column="4" colspan="2">
+        <widget class="QComboBox" name="ifaceCombo">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="editable">
+          <bool>true</bool>
+         </property>
+        </widget>
+       </item>
+       <item row="0" column="5">
+        <widget class="QComboBox" name="protoCombo">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Only TCP, UDP or UDPLITE are allowed&lt;/p&gt;&lt;p&gt;You can use regexp, i.e.: ^(TCP|UDP)$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="editable">
+          <bool>true</bool>
+         </property>
+         <property name="currentText">
+          <string>TCP</string>
+         </property>
+         <item>
+          <property name="text">
+           <string notr="true">TCP</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">UDP</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">UDPLITE</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">TCP6</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">UDP6</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">UDPLITE6</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>ICMP</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>ICMP6</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>SCTP</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>SCTP6</string>
+          </property>
+         </item>
+        </widget>
+       </item>
+       <item row="5" column="2" colspan="4">
+        <widget class="QLineEdit" name="dstHostLine">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="toolTip">
+          <string>Commas or spaces are not allowed to specify multiple domains. 
+
+Use regular expressions instead: 
+.*(opensnitch|duckduckgo).com
+.*\.google.com
+
+or a single domain:
+www.gnu.org - it'll only match www.gnu.org, nor ftp.gnu.org, nor www2.gnu.org, ...
+gnu.org         - it'll only match gnu.org, nor www.gnu.org, nor ftp.gnu.org, ...</string>
+         </property>
+         <property name="placeholderText">
+          <string>www.domain.org, .*\.domain.org</string>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="0">
+        <widget class="QCheckBox" name="dstIPCheck">
+         <property name="text">
+          <string>To this IP / Network</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="1">
+        <spacer name="horizontalSpacer_10">
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Maximum</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>60</width>
+           <height>20</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+       <item row="0" column="0">
+        <widget class="QCheckBox" name="protoCheck">
+         <property name="text">
+          <string>Protocol</string>
+         </property>
+        </widget>
+       </item>
+       <item row="4" column="2" colspan="4">
+        <widget class="QComboBox" name="srcIPCombo">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="toolTip">
+          <string>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</string>
+         </property>
+         <property name="editable">
+          <bool>true</bool>
+         </property>
+         <property name="currentText">
+          <string notr="true">LAN</string>
+         </property>
+         <item>
+          <property name="text">
+           <string>LAN</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>MULTICAST</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>127.0.0.0/8</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>192.168.0.0/24</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>192.168.1.0/24</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>192.168.2.0/24</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>192.168.0.0/16</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>169.254.0.0/16</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>172.16.0.0/12</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>10.0.0.0/8</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>::1/128</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>fc00::/7</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>ff00::/8</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>fe80::/10</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string>fd00::/8</string>
+          </property>
+         </item>
+        </widget>
+       </item>
+       <item row="4" column="0">
+        <widget class="QCheckBox" name="srcIPCheck">
+         <property name="text">
+          <string>From this IP / Network</string>
+         </property>
+        </widget>
+       </item>
+       <item row="5" column="0">
+        <widget class="QCheckBox" name="dstHostCheck">
+         <property name="text">
+          <string>To this host</string>
+         </property>
+        </widget>
+       </item>
+       <item row="6" column="2" colspan="4">
+        <widget class="QComboBox" name="dstIPCombo">
+         <property name="enabled">
+          <bool>false</bool>
+         </property>
+         <property name="toolTip">
+          <string>You can specify a single IP:
+- 192.168.1.1
+
+or a regular expression:
+- 192\.168\.1\.[0-9]+
+
+multiple IPs:
+- ^(192\.168\.1\.1|172\.16\.0\.1)$
+
+You can also specify a subnet:
+- 192.168.1.0/24
+
+Note: Commas or spaces are not allowed to separate IPs or networks.</string>
+         </property>
+         <property name="statusTip">
+          <string/>
+         </property>
+         <property name="editable">
+          <bool>true</bool>
+         </property>
+         <property name="currentText">
+          <string notr="true">LAN</string>
+         </property>
+         <item>
+          <property name="text">
+           <string notr="true">LAN</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">MULTICAST</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">127.0.0.0/8</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">192.168.0.0/24</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">192.168.1.0/24</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">192.168.2.0/24</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">192.168.0.0/16</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">169.254.0.0/16</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">172.16.0.0/12</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">10.0.0.0/8</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">::1/128</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">fc00::/7</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">ff00::/8</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">fe80::/10</string>
+          </property>
+         </item>
+         <item>
+          <property name="text">
+           <string notr="true">fd00::/8</string>
+          </property>
+         </item>
+        </widget>
+       </item>
+       <item row="7" column="0">
+        <widget class="QCheckBox" name="ifaceCheck">
+         <property name="text">
+          <string>Network interface</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0" colspan="6">
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <widget class="QCheckBox" name="srcPortCheck">
+           <property name="text">
+            <string>From this port</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="srcPortLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_3">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>20</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QCheckBox" name="dstPortCheck">
+           <property name="text">
+            <string>To this port</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="dstPortLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You can specify multiple ports using regular expressions:&lt;/p&gt;&lt;p&gt;- 53, 80 or 443:&lt;/p&gt;&lt;p&gt;^(53|80|443)$&lt;/p&gt;&lt;p&gt;&lt;br/&gt;&lt;/p&gt;&lt;p&gt;- 53, 443 or 5551, 5552, 5553, etc:&lt;/p&gt;&lt;p&gt;^(53|443|555[0-9])$&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tabWidgetPage3">
+      <attribute name="icon">
+       <iconset theme="document-properties">
+        <normaloff>.</normaloff>.</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>List of domains/IPs</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_5">
+       <item row="3" column="0">
+        <widget class="QCheckBox" name="dstListNetsCheck">
+         <property name="text">
+          <string>To this list of network ranges</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <widget class="QCheckBox" name="dstListIPsCheck">
+         <property name="text">
+          <string>To this list of IPs</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_7">
+         <item>
+          <widget class="QPushButton" name="selectIPsListButton">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="system-search">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="dstListIPsLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of IPs to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.4.5&lt;/p&gt;&lt;p&gt;1.2.3.4.6&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;/p&gt;&lt;p&gt;One IP per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="0" column="0">
+        <widget class="QCheckBox" name="dstListsCheck">
+         <property name="text">
+          <string>To this list of domains</string>
+         </property>
+        </widget>
+       </item>
+       <item row="3" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_9">
+         <item>
+          <widget class="QPushButton" name="selectNetsListButton">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="system-search">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="dstListNetsLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing list of network ranges to block or allow:&lt;/p&gt;&lt;p&gt;1.2.3.0/24&lt;/p&gt;&lt;p&gt;80.34.56.0/20&lt;/p&gt;&lt;p&gt;.&lt;/p&gt;&lt;p&gt;etc.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;One Network Range per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="0" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
+         <item>
+          <widget class="QPushButton" name="selectListButton">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="system-search">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="dstListsLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with lists of domains to block or allow.&lt;/p&gt;&lt;p&gt;Put inside that directory files with any extension containing lists of domains.&lt;/p&gt;&lt;p&gt;&lt;br/&gt;The format of each entry of a list is as follow (hosts format):&lt;/p&gt;&lt;p&gt;127.0.0.1 www.domain.com&lt;/p&gt;&lt;p&gt;or &lt;/p&gt;&lt;p&gt;0.0.0.0 www.domain.com&lt;/p&gt;&lt;p&gt;Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item row="1" column="0">
+        <widget class="QCheckBox" name="dstListRegexpCheck">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Maximum" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="text">
+          <string>To this list of domains 
+(regular expressions)</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="1">
+        <layout class="QHBoxLayout" name="horizontalLayout_10">
+         <item>
+          <widget class="QPushButton" name="selectListRegexpButton">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="system-search">
+             <normaloff>.</normaloff>.</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLineEdit" name="dstRegexpListsLine">
+           <property name="enabled">
+            <bool>false</bool>
+           </property>
+           <property name="toolTip">
+            <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select a directory with files containing regular expressions of domains to block or allow:&lt;/p&gt;&lt;p&gt;.*\.example\.com&lt;/p&gt;&lt;p&gt;You can also use a domain as is: &amp;quot;example.com&amp;quot; , and it'll match whatever.example.com, whatever.example.com.localdomain, etc.&lt;/p&gt;&lt;p&gt;One domain per line. Empty lines or started with # are ignored.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab">
+      <attribute name="title">
+       <string>More</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_6">
+       <item row="0" column="0">
+        <widget class="QCheckBox" name="sensitiveCheck">
+         <property name="toolTip">
+          <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;By default, the field of the rules are case-insensitive, i.e., if a process tries to access gOOgle.CoM and you have a rule to Deny .*google.com, the connection will be blocked.&lt;br/&gt;&lt;/p&gt;&lt;p&gt;If you check this box, you have to specify the exact string (domain, executable, command line) that you want to filter.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+         </property>
+         <property name="text">
+          <string>Case-sensitive</string>
+         </property>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <widget class="QCheckBox" name="nologCheck">
+         <property name="statusTip">
+          <string>Don't log connections that match this rule</string>
+         </property>
+         <property name="text">
+          <string>Don't log connections</string>
+         </property>
+        </widget>
+       </item>
+       <item row="2" column="0">
+        <spacer name="verticalSpacer">
+         <property name="orientation">
+          <enum>Qt::Vertical</enum>
+         </property>
+         <property name="sizeType">
+          <enum>QSizePolicy::Maximum</enum>
+         </property>
+         <property name="sizeHint" stdset="0">
+          <size>
+           <width>20</width>
+           <height>40</height>
+          </size>
+         </property>
+        </spacer>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item row="9" column="0">
+    <widget class="Line" name="line">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QPlainTextEdit" name="ruleDescEdit">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>0</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="maximumSize">
+      <size>
+       <width>16777215</width>
+       <height>60</height>
+      </size>
+     </property>
+     <property name="placeholderText">
+      <string>Description...</string>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/res/stats.ui b/ui/opensnitch/res/stats.ui
new file mode 100644 (file)
index 0000000..08c6294
--- /dev/null
@@ -0,0 +1,1844 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>StatsDialog</class>
+ <widget class="QDialog" name="StatsDialog">
+  <property name="windowModality">
+   <enum>Qt::NonModal</enum>
+  </property>
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>863</width>
+    <height>597</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="minimumSize">
+   <size>
+    <width>300</width>
+    <height>220</height>
+   </size>
+  </property>
+  <property name="font">
+   <font>
+    <kerning>true</kerning>
+   </font>
+  </property>
+  <property name="windowTitle">
+   <string>OpenSnitch Network Statistics</string>
+  </property>
+  <property name="windowIcon">
+   <iconset resource="resources.qrc">
+    <normaloff>:/pics/icon-white.svg</normaloff>
+    <normalon>:/pics/icon-white.svg</normalon>
+    <disabledoff>:/pics/icon-white.svg</disabledoff>
+    <disabledon>:/pics/icon-white.svg</disabledon>
+    <activeoff>:/pics/icon-white.svg</activeoff>
+    <activeon>:/pics/icon-white.svg</activeon>:/pics/icon-white.svg</iconset>
+  </property>
+  <property name="sizeGripEnabled">
+   <bool>false</bool>
+  </property>
+  <property name="modal">
+   <bool>false</bool>
+  </property>
+  <layout class="QGridLayout" name="gridLayout_4">
+   <property name="leftMargin">
+    <number>4</number>
+   </property>
+   <property name="topMargin">
+    <number>2</number>
+   </property>
+   <property name="rightMargin">
+    <number>4</number>
+   </property>
+   <property name="bottomMargin">
+    <number>4</number>
+   </property>
+   <property name="horizontalSpacing">
+    <number>2</number>
+   </property>
+   <property name="verticalSpacing">
+    <number>4</number>
+   </property>
+   <item row="3" column="0">
+    <widget class="QFrame" name="navToolBar">
+     <property name="frameShape">
+      <enum>QFrame::NoFrame</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QHBoxLayout" name="horizontalLayout_17">
+      <property name="leftMargin">
+       <number>4</number>
+      </property>
+      <property name="topMargin">
+       <number>2</number>
+      </property>
+      <property name="rightMargin">
+       <number>4</number>
+      </property>
+      <property name="bottomMargin">
+       <number>4</number>
+      </property>
+      <item>
+       <widget class="QLabel" name="label_4">
+        <property name="text">
+         <string>Filter</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="comboAction">
+        <item>
+         <property name="text">
+          <string>-</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Allow</string>
+         </property>
+         <property name="icon">
+          <iconset theme="emblem-default">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Deny</string>
+         </property>
+         <property name="icon">
+          <iconset theme="emblem-important">
+           <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>Reject</string>
+         </property>
+         <property name="icon">
+          <iconset theme="window-close">
+           <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLineEdit" name="filterLine">
+        <property name="text">
+         <string notr="true"/>
+        </property>
+        <property name="frame">
+         <bool>true</bool>
+        </property>
+        <property name="placeholderText">
+         <string>Ex.: firefox</string>
+        </property>
+        <property name="clearButtonEnabled">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <spacer name="horizontalSpacer_3">
+        <property name="orientation">
+         <enum>Qt::Horizontal</enum>
+        </property>
+        <property name="sizeType">
+         <enum>QSizePolicy::Minimum</enum>
+        </property>
+        <property name="sizeHint" stdset="0">
+         <size>
+          <width>20</width>
+          <height>20</height>
+         </size>
+        </property>
+       </spacer>
+      </item>
+      <item>
+       <widget class="QPushButton" name="prevButton">
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="go-previous">
+          <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QLabel" name="labelRowsCount">
+        <property name="text">
+         <string>0</string>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="nextButton">
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="go-next">
+          <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QComboBox" name="limitCombo">
+        <property name="currentIndex">
+         <number>0</number>
+        </property>
+        <property name="sizeAdjustPolicy">
+         <enum>QComboBox::AdjustToContents</enum>
+        </property>
+        <item>
+         <property name="text">
+          <string>50</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>100</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>200</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string>300</string>
+         </property>
+        </item>
+        <item>
+         <property name="text">
+          <string/>
+         </property>
+        </item>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="cmdCleanSql">
+        <property name="toolTip">
+         <string>Delete all intercepted events</string>
+        </property>
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="edit-clear-all">
+          <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="QPushButton" name="helpButton">
+        <property name="text">
+         <string/>
+        </property>
+        <property name="icon">
+         <iconset theme="help-browser">
+          <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+        </property>
+        <property name="flat">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="0" column="0">
+    <widget class="QFrame" name="frame">
+     <property name="frameShape">
+      <enum>QFrame::StyledPanel</enum>
+     </property>
+     <property name="frameShadow">
+      <enum>QFrame::Raised</enum>
+     </property>
+     <layout class="QGridLayout" name="gridLayout">
+      <property name="leftMargin">
+       <number>0</number>
+      </property>
+      <property name="topMargin">
+       <number>0</number>
+      </property>
+      <property name="rightMargin">
+       <number>0</number>
+      </property>
+      <property name="bottomMargin">
+       <number>0</number>
+      </property>
+      <property name="spacing">
+       <number>0</number>
+      </property>
+      <item row="0" column="0">
+       <layout class="QHBoxLayout" name="horizontalLayout_10">
+        <item>
+         <widget class="QPushButton" name="actionsButton">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="icon">
+           <iconset theme="format-justify-fill">
+            <normaloff>.</normaloff>.</iconset>
+          </property>
+          <property name="flat">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="prefsButton">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="icon">
+           <iconset theme="preferences-system">
+            <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+          </property>
+          <property name="flat">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="newRuleButton">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+            <horstretch>32</horstretch>
+            <verstretch>32</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="toolTip">
+           <string>Create a new rule</string>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="icon">
+           <iconset theme="document-new">
+            <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+          </property>
+          <property name="flat">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="fwButton">
+          <property name="text">
+           <string/>
+          </property>
+          <property name="icon">
+           <iconset theme="security-high"/>
+          </property>
+          <property name="flat">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer_9">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>60</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QLabel" name="nodeLabel">
+          <property name="text">
+           <string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-size:11pt; font-weight:600;&quot;&gt;hostname - 192.168.1.1&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
+          </property>
+          <property name="margin">
+           <number>5</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <spacer name="horizontalSpacer">
+          <property name="orientation">
+           <enum>Qt::Horizontal</enum>
+          </property>
+          <property name="sizeHint" stdset="0">
+           <size>
+            <width>40</width>
+            <height>20</height>
+           </size>
+          </property>
+         </spacer>
+        </item>
+        <item>
+         <widget class="QLabel" name="label_2">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="font">
+           <font>
+            <pointsize>11</pointsize>
+            <weight>75</weight>
+            <bold>true</bold>
+            <kerning>true</kerning>
+           </font>
+          </property>
+          <property name="text">
+           <string>Status</string>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+          </property>
+          <property name="margin">
+           <number>5</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QLabel" name="statusLabel">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="font">
+           <font>
+            <pointsize>11</pointsize>
+            <kerning>true</kerning>
+           </font>
+          </property>
+          <property name="text">
+           <string>-</string>
+          </property>
+          <property name="alignment">
+           <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+          </property>
+          <property name="margin">
+           <number>5</number>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QPushButton" name="startButton">
+          <property name="toolTip">
+           <string>Start or Stop interception</string>
+          </property>
+          <property name="text">
+           <string/>
+          </property>
+          <property name="icon">
+           <iconset theme="media-playback-start">
+            <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+          </property>
+          <property name="checkable">
+           <bool>true</bool>
+          </property>
+          <property name="checked">
+           <bool>false</bool>
+          </property>
+          <property name="flat">
+           <bool>true</bool>
+          </property>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item row="1" column="0">
+    <widget class="QTabWidget" name="tabWidget">
+     <property name="tabPosition">
+      <enum>QTabWidget::North</enum>
+     </property>
+     <property name="tabShape">
+      <enum>QTabWidget::Rounded</enum>
+     </property>
+     <property name="currentIndex">
+      <number>0</number>
+     </property>
+     <property name="documentMode">
+      <bool>true</bool>
+     </property>
+     <widget class="QWidget" name="tab">
+      <attribute name="icon">
+       <iconset theme="view-sort-ascending">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Events</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_8">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>4</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_16">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="eventsTable">
+           <property name="styleSheet">
+            <string notr="true"/>
+           </property>
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Plain</enum>
+           </property>
+           <property name="autoScroll">
+            <bool>false</bool>
+           </property>
+           <property name="editTriggers">
+            <set>QAbstractItemView::NoEditTriggers</set>
+           </property>
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="horizontalScrollMode">
+            <enum>QAbstractItemView::ScrollPerPixel</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <attribute name="verticalHeaderVisible">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="connectionsTableScrollBar">
+           <property name="styleSheet">
+            <string notr="true"/>
+           </property>
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_8">
+      <attribute name="icon">
+       <iconset theme="network-workgroup">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Nodes</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="topMargin">
+        <number>9</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_12">
+         <item>
+          <widget class="QPushButton" name="cmdNodesBack">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="nodesLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <spacer name="horizontalSpacer_2">
+           <property name="orientation">
+            <enum>Qt::Horizontal</enum>
+           </property>
+           <property name="sizeHint" stdset="0">
+            <size>
+             <width>40</width>
+             <height>20</height>
+            </size>
+           </property>
+          </spacer>
+         </item>
+         <item>
+          <widget class="QPushButton" name="nodeActionsButton">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="format-justify-fill"/>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="nodeDeleteButton">
+           <property name="toolTip">
+            <string>Delete this node</string>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="edit-delete"/>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="nodePrefsButton">
+           <property name="toolTip">
+            <string>Show the preferences of this node</string>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="preferences-system"/>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="nodeStartButton">
+           <property name="toolTip">
+            <string>Start or stop interception of this node</string>
+           </property>
+           <property name="text">
+            <string notr="true"/>
+           </property>
+           <property name="icon">
+            <iconset theme="media-playback-start"/>
+           </property>
+           <property name="checkable">
+            <bool>true</bool>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="nodesTable">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Plain</enum>
+           </property>
+           <property name="autoScroll">
+            <bool>false</bool>
+           </property>
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <property name="sortingEnabled">
+            <bool>true</bool>
+           </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
+           <attribute name="verticalHeaderVisible">
+            <bool>false</bool>
+           </attribute>
+           <attribute name="verticalHeaderStretchLastSection">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="verticalScrollBar">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_3">
+      <attribute name="icon">
+       <iconset theme="address-book-new">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Rules</string>
+      </attribute>
+      <layout class="QGridLayout" name="gridLayout_2">
+       <item row="2" column="0">
+        <widget class="QSplitter" name="rulesSplitter">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="orientation">
+          <enum>Qt::Horizontal</enum>
+         </property>
+         <widget class="QTreeWidget" name="rulesTreePanel">
+          <property name="sizePolicy">
+           <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
+            <horstretch>0</horstretch>
+            <verstretch>0</verstretch>
+           </sizepolicy>
+          </property>
+          <property name="animated">
+           <bool>true</bool>
+          </property>
+          <property name="headerHidden">
+           <bool>true</bool>
+          </property>
+          <attribute name="headerStretchLastSection">
+           <bool>false</bool>
+          </attribute>
+          <column>
+           <property name="text">
+            <string notr="true">1</string>
+           </property>
+          </column>
+          <column>
+           <property name="text">
+            <string>2</string>
+           </property>
+          </column>
+          <item>
+           <property name="text">
+            <string>Application rules</string>
+           </property>
+           <property name="font">
+            <font>
+             <pointsize>10</pointsize>
+             <weight>75</weight>
+             <bold>true</bold>
+            </font>
+           </property>
+           <property name="icon">
+            <iconset theme="system-run">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
+           </property>
+           <item>
+            <property name="text">
+             <string>Permanent</string>
+            </property>
+            <property name="font">
+             <font>
+              <pointsize>10</pointsize>
+             </font>
+            </property>
+            <property name="icon">
+             <iconset theme="security-medium">
+              <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
+            </property>
+           </item>
+           <item>
+            <property name="text">
+             <string>Temporary</string>
+            </property>
+            <property name="font">
+             <font>
+              <pointsize>10</pointsize>
+             </font>
+            </property>
+            <property name="icon">
+             <iconset theme="edit-clear">
+              <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
+            </property>
+           </item>
+          </item>
+          <item>
+           <property name="text">
+            <string>Nodes</string>
+           </property>
+           <property name="font">
+            <font>
+             <pointsize>10</pointsize>
+             <weight>75</weight>
+             <bold>true</bold>
+            </font>
+           </property>
+           <property name="icon">
+            <iconset theme="network-workgroup">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../gustavo-iniguez-goya/opensnitch/ui/opensnitch/res</iconset>
+           </property>
+          </item>
+          <item>
+           <property name="text">
+            <string>System rules</string>
+           </property>
+           <property name="font">
+            <font>
+             <weight>75</weight>
+             <bold>true</bold>
+            </font>
+           </property>
+           <property name="icon">
+            <iconset theme="security-high">
+             <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </item>
+         </widget>
+         <widget class="QWidget" name="horizontalLayoutWidget">
+          <layout class="QHBoxLayout" name="horizontalLayout_19">
+           <property name="spacing">
+            <number>0</number>
+           </property>
+           <item>
+            <widget class="FirewallTableView" name="fwTable">
+             <property name="autoScroll">
+              <bool>false</bool>
+             </property>
+             <property name="editTriggers">
+              <set>QAbstractItemView::AnyKeyPressed|QAbstractItemView::EditKeyPressed</set>
+             </property>
+             <property name="alternatingRowColors">
+              <bool>true</bool>
+             </property>
+             <property name="selectionBehavior">
+              <enum>QAbstractItemView::SelectRows</enum>
+             </property>
+             <property name="horizontalScrollMode">
+              <enum>QAbstractItemView::ScrollPerPixel</enum>
+             </property>
+             <property name="showGrid">
+              <bool>false</bool>
+             </property>
+             <property name="sortingEnabled">
+              <bool>true</bool>
+             </property>
+             <attribute name="horizontalHeaderStretchLastSection">
+              <bool>true</bool>
+             </attribute>
+             <attribute name="verticalHeaderMinimumSectionSize">
+              <number>25</number>
+             </attribute>
+             <attribute name="verticalHeaderDefaultSectionSize">
+              <number>42</number>
+             </attribute>
+            </widget>
+           </item>
+           <item>
+            <widget class="GenericTableView" name="rulesTable">
+             <property name="selectionBehavior">
+              <enum>QAbstractItemView::SelectRows</enum>
+             </property>
+             <property name="horizontalScrollMode">
+              <enum>QAbstractItemView::ScrollPerPixel</enum>
+             </property>
+             <property name="showGrid">
+              <bool>false</bool>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QScrollBar" name="rulesScrollBar">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </widget>
+       </item>
+       <item row="1" column="0">
+        <layout class="QVBoxLayout" name="verticalLayout_2">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_8">
+           <item>
+            <widget class="QComboBox" name="comboRulesFilter">
+             <item>
+              <property name="text">
+               <string>All applications</string>
+              </property>
+              <property name="icon">
+               <iconset theme="system-run">
+                <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Permanent</string>
+              </property>
+              <property name="icon">
+               <iconset theme="security-medium">
+                <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>Temporary</string>
+              </property>
+              <property name="icon">
+               <iconset theme="edit-clear">
+                <normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>System rules</string>
+              </property>
+              <property name="icon">
+               <iconset theme="security-high"/>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_11">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>30</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+       <item row="0" column="0">
+        <layout class="QHBoxLayout" name="rulesToolbarLayout">
+         <item>
+          <widget class="QPushButton" name="cmdRulesBack">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QCheckBox" name="enableRuleCheck">
+           <property name="text">
+            <string>enable</string>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="nodeRuleLabel">
+           <property name="text">
+            <string/>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="ruleLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="editRuleButton">
+           <property name="toolTip">
+            <string>Edit rule</string>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="accessories-text-editor">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="delRuleButton">
+           <property name="toolTip">
+            <string>Delete rule</string>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="edit-delete">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_4">
+      <attribute name="icon">
+       <iconset theme="computer">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Hosts</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_3">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_3">
+         <item>
+          <widget class="QPushButton" name="cmdHostsBack">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="hostsLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_7">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="hostsTable">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="horizontalScrollMode">
+            <enum>QAbstractItemView::ScrollPerPixel</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <property name="sortingEnabled">
+            <bool>false</bool>
+           </property>
+           <property name="cornerButtonEnabled">
+            <bool>false</bool>
+           </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
+           <attribute name="verticalHeaderStretchLastSection">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="hostsScrollBar">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_7">
+      <attribute name="icon">
+       <iconset theme="system-run">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Applications</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_4">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_2">
+         <item>
+          <widget class="QPushButton" name="cmdProcsBack">
+           <property name="enabled">
+            <bool>true</bool>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="procsLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="styleSheet">
+            <string notr="true"/>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QPushButton" name="cmdProcDetails">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="system-search">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+           <property name="flat">
+            <bool>true</bool>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_11">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="procsTable">
+           <property name="frameShape">
+            <enum>QFrame::NoFrame</enum>
+           </property>
+           <property name="frameShadow">
+            <enum>QFrame::Plain</enum>
+           </property>
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="horizontalScrollMode">
+            <enum>QAbstractItemView::ScrollPerPixel</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <property name="sortingEnabled">
+            <bool>false</bool>
+           </property>
+           <property name="cornerButtonEnabled">
+            <bool>true</bool>
+           </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
+           <attribute name="verticalHeaderStretchLastSection">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="procsScrollBar">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_2">
+      <attribute name="icon">
+       <iconset theme="network-server">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Addresses</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_5">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_4">
+         <item>
+          <widget class="QPushButton" name="cmdAddrsBack">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="addrsLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_13">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="addrTable">
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="horizontalScrollMode">
+            <enum>QAbstractItemView::ScrollPerPixel</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <property name="sortingEnabled">
+            <bool>false</bool>
+           </property>
+           <property name="cornerButtonEnabled">
+            <bool>false</bool>
+           </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
+           <attribute name="verticalHeaderStretchLastSection">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="addrsScrollBar">
+           <property name="maximum">
+            <number>50</number>
+           </property>
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_5">
+      <attribute name="icon">
+       <iconset theme="network-wired">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Ports</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_6">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_5">
+         <item>
+          <widget class="QPushButton" name="cmdPortsBack">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="portsLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_14">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="portsTable">
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <property name="sortingEnabled">
+            <bool>false</bool>
+           </property>
+           <property name="cornerButtonEnabled">
+            <bool>false</bool>
+           </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
+           <attribute name="verticalHeaderStretchLastSection">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="portsScrollBar">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+     <widget class="QWidget" name="tab_6">
+      <attribute name="icon">
+       <iconset theme="system-users">
+        <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+      </attribute>
+      <attribute name="title">
+       <string>Users</string>
+      </attribute>
+      <layout class="QVBoxLayout" name="verticalLayout_7">
+       <property name="leftMargin">
+        <number>0</number>
+       </property>
+       <property name="rightMargin">
+        <number>0</number>
+       </property>
+       <property name="bottomMargin">
+        <number>0</number>
+       </property>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_6">
+         <item>
+          <widget class="QPushButton" name="cmdUsersBack">
+           <property name="text">
+            <string/>
+           </property>
+           <property name="icon">
+            <iconset theme="go-previous">
+             <normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</normaloff>../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../../.designer/backup</iconset>
+           </property>
+          </widget>
+         </item>
+         <item>
+          <widget class="QLabel" name="usersLabel">
+           <property name="sizePolicy">
+            <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <horstretch>0</horstretch>
+             <verstretch>0</verstretch>
+            </sizepolicy>
+           </property>
+           <property name="text">
+            <string/>
+           </property>
+           <property name="textInteractionFlags">
+            <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QHBoxLayout" name="horizontalLayout_15">
+         <property name="spacing">
+          <number>0</number>
+         </property>
+         <item>
+          <widget class="GenericTableView" name="usersTable">
+           <property name="selectionBehavior">
+            <enum>QAbstractItemView::SelectRows</enum>
+           </property>
+           <property name="showGrid">
+            <bool>false</bool>
+           </property>
+           <property name="sortingEnabled">
+            <bool>false</bool>
+           </property>
+           <property name="cornerButtonEnabled">
+            <bool>false</bool>
+           </property>
+           <attribute name="horizontalHeaderStretchLastSection">
+            <bool>true</bool>
+           </attribute>
+           <attribute name="verticalHeaderStretchLastSection">
+            <bool>false</bool>
+           </attribute>
+          </widget>
+         </item>
+         <item>
+          <widget class="QScrollBar" name="usersScrollBar">
+           <property name="orientation">
+            <enum>Qt::Vertical</enum>
+           </property>
+          </widget>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </widget>
+   </item>
+   <item row="4" column="0">
+    <layout class="QHBoxLayout" name="horizontalLayout_9">
+     <property name="spacing">
+      <number>6</number>
+     </property>
+     <item>
+      <layout class="QHBoxLayout" name="statsLayout">
+       <item>
+        <widget class="QLabel" name="label_5">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>10</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>Connections</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="consLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>8</pointsize>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>-</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_7">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>10</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>Dropped</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="droppedLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>8</pointsize>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>-</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>10</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>Uptime</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="uptimeLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>8</pointsize>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>-</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="label_3">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>10</pointsize>
+           <weight>75</weight>
+           <bold>true</bold>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>Rules</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+       <item>
+        <widget class="QLabel" name="rulesLabel">
+         <property name="sizePolicy">
+          <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+           <horstretch>0</horstretch>
+           <verstretch>0</verstretch>
+          </sizepolicy>
+         </property>
+         <property name="font">
+          <font>
+           <pointsize>8</pointsize>
+           <kerning>true</kerning>
+          </font>
+         </property>
+         <property name="text">
+          <string>-</string>
+         </property>
+         <property name="alignment">
+          <set>Qt::AlignCenter</set>
+         </property>
+         <property name="margin">
+          <number>5</number>
+         </property>
+        </widget>
+       </item>
+      </layout>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer_10">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+     <item>
+      <widget class="QLabel" name="label_6">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <pointsize>10</pointsize>
+         <weight>75</weight>
+         <bold>true</bold>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>Version</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+       </property>
+       <property name="margin">
+        <number>5</number>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QLabel" name="daemonVerLabel">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="font">
+        <font>
+         <family>DejaVu Sans</family>
+         <pointsize>8</pointsize>
+         <kerning>true</kerning>
+        </font>
+       </property>
+       <property name="text">
+        <string>-</string>
+       </property>
+       <property name="alignment">
+        <set>Qt::AlignCenter</set>
+       </property>
+       <property name="margin">
+        <number>5</number>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>GenericTableView</class>
+   <extends>QTableView</extends>
+   <header>customwidgets.generictableview</header>
+  </customwidget>
+  <customwidget>
+   <class>FirewallTableView</class>
+   <extends>QTableView</extends>
+   <header>customwidgets.firewalltableview</header>
+  </customwidget>
+ </customwidgets>
+ <resources>
+  <include location="resources.qrc"/>
+ </resources>
+ <connections/>
+</ui>
diff --git a/ui/opensnitch/rules.py b/ui/opensnitch/rules.py
new file mode 100644 (file)
index 0000000..485277b
--- /dev/null
@@ -0,0 +1,287 @@
+from PyQt5.QtCore import QObject, pyqtSignal
+
+from opensnitch.database import Database
+from opensnitch.database.enums import RuleFields
+from opensnitch.config import Config
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+import os
+import json
+from slugify import slugify
+from datetime import datetime
+from google.protobuf.json_format import MessageToJson, Parse
+
+DefaultRulesPath = "/etc/opensnitchd/rules"
+
+# date format displayed on the GUI (created column)
+DBDateFieldFormat = "%Y-%m-%d %H:%M:%S"
+
+class Rule():
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def to_bool(s):
+        return s == 'True'
+
+    @staticmethod
+    def new_empty():
+        pass
+
+    @staticmethod
+    def new_from_records(records):
+        """Creates a new protobuf Rule from DB records.
+        Fields of the record are in the order defined on the DB.
+        """
+        rule = ui_pb2.Rule(name=records.value(RuleFields.Name))
+        rule.enabled = Rule.to_bool(records.value(RuleFields.Enabled))
+        rule.precedence = Rule.to_bool(records.value(RuleFields.Precedence))
+        rule.action = records.value(RuleFields.Action)
+        rule.duration = records.value(RuleFields.Duration)
+        rule.operator.type = records.value(RuleFields.OpType)
+        rule.operator.sensitive = Rule.to_bool(records.value(RuleFields.OpSensitive))
+        rule.operator.operand = records.value(RuleFields.OpOperand)
+        rule.operator.data = "" if records.value(RuleFields.OpData) == None else str(records.value(RuleFields.OpData))
+        rule.description = records.value(RuleFields.Description)
+        rule.nolog = Rule.to_bool(records.value(RuleFields.NoLog))
+        created = int(datetime.now().timestamp())
+        if records.value(RuleFields.Created) != "":
+            created = int(datetime.strptime(
+                records.value(RuleFields.Created), DBDateFieldFormat
+            ).timestamp())
+        rule.created = created
+
+        try:
+            # Operator list is always saved as json string to the db,
+            # so we need to load the json string.
+            if rule.operator.type == Config.RULE_TYPE_LIST:
+                operators = json.loads(rule.operator.data)
+                for op in operators:
+                    rule.operator.list.extend([
+                        ui_pb2.Operator(
+                            type=op['type'],
+                            operand=op['operand'],
+                            sensitive=False if op.get('sensitive') == None else op['sensitive'],
+                            data="" if op.get('data') == None else op['data']
+                        )
+                    ])
+                rule.operator.data = ""
+        except Exception as e:
+            print("new_from_records exception parsing operartor list:", e)
+
+
+        return rule
+
+class Rules(QObject):
+    __instance = None
+    updated = pyqtSignal(int)
+
+    LOG_TAG = "[Rules]: "
+
+    @staticmethod
+    def instance():
+        if Rules.__instance == None:
+            Rules.__instance = Rules()
+        return Rules.__instance
+
+    def __init__(self):
+        QObject.__init__(self)
+        self._db = Database.instance()
+
+    def add(self, time, node, name, description, enabled, precedence, nolog, action, duration, op_type, op_sensitive, op_operand, op_data, created):
+        # don't add rule if the user has selected to exclude temporary
+        # rules
+        if duration in Config.RULES_DURATION_FILTER:
+            return
+
+        self._db.insert("rules",
+                  "(time, node, name, description, enabled, precedence, nolog, action, duration, operator_type, operator_sensitive, operator_operand, operator_data, created)",
+                  (time, node, name, description, enabled, precedence, nolog, action, duration, op_type, op_sensitive, op_operand, op_data, created),
+                        action_on_conflict="REPLACE")
+
+    def add_rules(self, addr, rules):
+        try:
+            for _,r in enumerate(rules):
+                # Operator list is always saved as json string to the db.
+                rjson = json.loads(MessageToJson(r))
+                if r.operator.type == Config.RULE_TYPE_LIST and rjson.get('operator') != None and rjson.get('operator').get('list') != None:
+                    r.operator.data = json.dumps(rjson.get('operator').get('list'))
+
+                self.add(datetime.now().strftime(DBDateFieldFormat),
+                         addr,
+                         r.name, r.description, str(r.enabled),
+                         str(r.precedence), str(r.nolog), r.action, r.duration,
+                         r.operator.type,
+                         str(r.operator.sensitive),
+                         r.operator.operand, r.operator.data,
+                         str(datetime.fromtimestamp(r.created).strftime(DBDateFieldFormat)))
+
+            return True
+        except Exception as e:
+            print(self.LOG_TAG + " exception adding node rules to db: ", e)
+            return False
+
+    def delete(self, name, addr, callback):
+        rule = ui_pb2.Rule(name=name)
+        rule.enabled = False
+        rule.action = ""
+        rule.duration = ""
+        rule.operator.type = ""
+        rule.operator.operand = ""
+        rule.operator.data = ""
+
+        if not self._db.delete_rule(rule.name, addr):
+            return None
+
+        return rule
+
+    def delete_by_field(self, field, values):
+        return self._db.delete_rules_by_field(field, values)
+
+    def exists(self, rule, node_addr):
+        return self._db.rule_exists(rule, node_addr)
+
+    def new_unique_name(self, rule_name, node_addr, prefix):
+        """generate a new name, if the supplied one already exists
+        """
+        if self._db.get_rule(rule_name, node_addr).next() == False:
+            return rule_name
+
+        for idx in range(0, 100):
+            new_rule_name = "{0}-{1}".format(rule_name, idx)
+            if self._db.get_rule(new_rule_name, node_addr).next() == False:
+                return new_rule_name
+
+        return rule_name
+
+    def update_time(self, time, name, addr):
+        """Updates the time of a rule, whenever a new connection matched a
+        rule.
+        """
+        self._db.update("rules",
+                        "time=?",
+                        (time, name, addr),
+                        "name=? AND node=?",
+                        action_on_conflict="OR REPLACE"
+                        )
+
+    def _timestamp_to_rfc3339(self, time):
+        """converts timestamp to rfc3339 format"""
+        return "{0}Z".format(
+            datetime.fromtimestamp(time).isoformat(timespec='microseconds')
+        )
+
+    def rule_to_json(self, node, rule_name):
+        try:
+            records = self._db.get_rule(rule_name, node)
+            if records == None or records == -1:
+                return None
+            if not records.next():
+                return None
+            rule = Rule.new_from_records(records)
+            # exclude this field when exporting to json
+            tempRule = MessageToJson(rule)
+            jRule = json.loads(tempRule)
+            jRule['created'] = self._timestamp_to_rfc3339(rule.created)
+            return json.dumps(jRule, indent="    ")
+        except Exception as e:
+            print("rule_to_json() exception:", e)
+            return None
+
+    def _export_rule_common(self, node, records, outdir):
+        try:
+            rule = Rule.new_from_records(records)
+            rulename = rule.name
+            if ".json" not in rulename:
+                rulename = rulename + ".json"
+            with open(outdir  + "/" + rulename, 'w') as jsfile:
+                actual_json_text = MessageToJson(rule)
+                jRule = json.loads(actual_json_text)
+                jRule['created'] = self._timestamp_to_rfc3339(rule.created)
+                actual_json_text = json.dumps(jRule, indent="    ")
+                jsfile.write( actual_json_text )
+
+            return True
+        except Exception as e:
+            print(self.LOG_TAG, "export_rules(", node, outdir, ") exception:", e)
+
+        return False
+
+    def export_rule(self, node, rule_name, outdir):
+        """Gets the rule from the DB and writes it out to a directory.
+        A new directory per node will be created.
+        """
+        try:
+            records = self._db.get_rule(rule_name, node)
+            if records.next() == False:
+                print("export_rule() get_error 2:", records)
+                return False
+
+            rulesdir = outdir + "/" + slugify(node)
+            try:
+                os.makedirs(rulesdir, 0o700)
+            except Exception as e:
+                print("exception creating dirs:", e)
+
+            return self._export_rule_common(node, records, rulesdir)
+
+        except Exception as e:
+            print(self.LOG_TAG, "export_rules(", node, rulesdir, ") exception:", e)
+
+        return False
+
+    def export_rules(self, node, outdir):
+        """Gets the rules from the DB and writes them out to a directory.
+        A new directory per node will be created.
+        """
+        records = self._db.get_rules(node)
+        if records == None:
+            return False
+
+        rulesdir = outdir + "/" + slugify(node)
+        try:
+            os.makedirs(rulesdir, 0o700)
+        except Exception as e:
+            print("exception creating dirs:", e)
+        try:
+            while records.next() != False:
+                self._export_rule_common(node, records, rulesdir)
+
+        except Exception as e:
+            print(self.LOG_TAG, "export_rules(", node, rulesdir, ") exception:", e)
+            return False
+
+        return True
+
+    def import_rules(self, rulesdir):
+        """Read a directory with rules in json format, and parse them out to protobuf
+        Returns a list of rules on success, or None on error.
+        """
+        try:
+            rules = []
+            for rulename in os.listdir(rulesdir):
+                with open(rulesdir  + "/" + rulename, 'r') as f:
+                    jsrule = f.read()
+                    # up until v1.6.5/v1.7.0, 'created' field was exported as timestamp.
+                    # since > v1.6.5 it's exported in rfc3339 format, so if we fail to
+                    # parse the rule, we'll try to convert the 'created' value from
+                    # timestamp to rfc3339.
+                    try:
+                        pb_rule = Parse(text=jsrule, message=ui_pb2.Rule(), ignore_unknown_fields=True)
+                    except:
+                        jRule = json.loads(jsrule)
+                        created = int(datetime.strptime(
+                            jRule['created'], "%Y-%m-%dT%H:%M:%S.%fZ"
+                        ).timestamp())
+                        jRule['created'] = created
+                        jsrule = json.dumps(jRule)
+                        pb_rule = Parse(text=jsrule, message=ui_pb2.Rule(), ignore_unknown_fields=True)
+                    rules.append(pb_rule)
+
+            return rules
+        except Exception as e:
+            print(self.LOG_TAG, "import_rules() exception:", e)
+
+        return None
diff --git a/ui/opensnitch/service.py b/ui/opensnitch/service.py
new file mode 100644 (file)
index 0000000..e983556
--- /dev/null
@@ -0,0 +1,907 @@
+from PyQt5 import QtWidgets, QtGui, QtCore
+from PyQt5.QtCore import QCoreApplication as QC
+
+from datetime import datetime, timedelta
+from threading import Thread, Lock, Event
+import grpc
+import os
+import sys
+import json
+import copy
+
+path = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(path)
+
+import opensnitch.proto as proto
+ui_pb2, ui_pb2_grpc = proto.import_()
+
+from opensnitch.dialogs.prompt import PromptDialog
+from opensnitch.dialogs.stats import StatsDialog
+
+from opensnitch.notifications import DesktopNotifications
+from opensnitch.firewall import Rules as FwRules
+from opensnitch.nodes import Nodes
+from opensnitch.config import Config
+from opensnitch.version import version
+from opensnitch.database import Database
+from opensnitch.utils import Utils, CleanerTask, Themes
+from opensnitch.utils import Message, languages
+from opensnitch.utils.xdg import Autostart
+
+class UIService(ui_pb2_grpc.UIServicer, QtWidgets.QGraphicsObject):
+    _new_remote_trigger = QtCore.pyqtSignal(str, ui_pb2.PingRequest)
+    _node_actions_trigger = QtCore.pyqtSignal(dict)
+    _update_stats_trigger = QtCore.pyqtSignal(str, str, ui_pb2.PingRequest)
+    _add_alert_trigger = QtCore.pyqtSignal(str, str, ui_pb2.Alert)
+    _version_warning_trigger = QtCore.pyqtSignal(str, str)
+    _status_change_trigger = QtCore.pyqtSignal(bool)
+    _notification_callback = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+    _show_message_trigger = QtCore.pyqtSignal(str, str, int, int)
+
+    # .desktop filename located under /usr/share/applications/
+    DESKTOP_FILENAME = "opensnitch_ui.desktop"
+
+    def __init__(self, app, on_exit, start_in_bg=False):
+        super(UIService, self).__init__()
+
+
+        self.MENU_ENTRY_STATS = QtCore.QCoreApplication.translate("contextual_menu", "Open main window")
+        self.MENU_ENTRY_FW_ENABLE = QtCore.QCoreApplication.translate("contextual_menu", "Enable")
+        self.MENU_ENTRY_FW_DISABLE = QtCore.QCoreApplication.translate("contextual_menu", "Disable")
+        self.MENU_ENTRY_HELP = QtCore.QCoreApplication.translate("contextual_menu", "Help")
+        self.MENU_ENTRY_CLOSE = QtCore.QCoreApplication.translate("contextual_menu", "Close")
+
+        # set of actions that must be performed on the main thread
+        self.NODE_ADD = 0
+        self.NODE_UPDATE = 1
+        self.NODE_DELETE = 2
+        self.ADD_RULE = 3
+        self.DELETE_RULE = 4
+
+        self._cfg = Config.init()
+        self._db = Database.instance()
+        db_file=self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY)
+        db_jrnl_wal=self._cfg.getBool(Config.DEFAULT_DB_JRNL_WAL)
+        db_status, db_error = self._db.initialize(
+            dbtype=self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY),
+            dbfile=db_file,
+            dbjrnl_wal=db_jrnl_wal
+        )
+        if db_status is False:
+            Message.ok(
+                QtCore.QCoreApplication.translate("preferences", "Warning"),
+                QtCore.QCoreApplication.translate("preferences",
+                                                  "The DB is corrupted and it's not safe to continue.<br>\
+                                                  Remove, backup or recover the file before continuing.<br><br>\
+                                                  Corrupted database file: {0}".format(db_file)),
+                QtWidgets.QMessageBox.Warning)
+            sys.exit(-1)
+
+        self._db_sqlite = self._db.get_db()
+        self._last_ping = None
+        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
+        self._exit = False
+        self._msg = QtWidgets.QMessageBox()
+        self._remote_lock = Lock()
+        self._remote_stats = {}
+        self._autostart = Autostart()
+
+        self.translator = None
+        self._init_translation()
+        self._themes = Themes()
+        self._desktop_notifications = DesktopNotifications()
+        self._setup_interfaces()
+        self._setup_icons()
+        self._prompt_dialog = PromptDialog(appicon=self.white_icon)
+        self._stats_dialog = StatsDialog(dbname="general", db=self._db, appicon=self.white_icon)
+        self._setup_tray()
+        self._setup_slots()
+
+        self._nodes = Nodes.instance()
+        self._nodes.reset_status()
+
+        self._last_stats = {}
+        self._last_items = {
+                'hosts':{},
+                'procs':{},
+                'addrs':{},
+                'ports':{},
+                'users':{}
+                }
+
+        if not start_in_bg:
+            self._show_gui_if_tray_not_available()
+
+        self._cleaner = None
+        if self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST):
+            self._start_db_cleaner()
+        self._cfg.setRulesDurationFilter(
+            self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES),
+            self._cfg.getInt(self._cfg.DEFAULT_IGNORE_TEMPORARY_RULES)
+        )
+        if self._cfg.getBool(self._cfg.DEFAULT_IGNORE_RULES):
+            self._nodes.delete_rule_by_field(Config.DURATION_FIELD, Config.RULES_DURATION_FILTER)
+
+    # https://gist.github.com/pklaus/289646
+    def _setup_interfaces(self):
+        namestr, outbytes = Utils.get_interfaces()
+        self._interfaces = {}
+        for i in range(0, outbytes, 40):
+            name = namestr[i:i+16].split(b'\0', 1)[0]
+            addr = namestr[i+20:i+24]
+            self._interfaces[name] = "%d.%d.%d.%d" % (int(addr[0]), int(addr[1]), int(addr[2]), int(addr[3]))
+
+    def _setup_slots(self):
+        # 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._new_remote_trigger.connect(self._on_new_remote)
+        self._node_actions_trigger.connect(self._on_node_actions)
+        self._update_stats_trigger.connect(self._on_update_stats)
+        self._add_alert_trigger.connect(self._on_new_alert)
+        self._status_change_trigger.connect(self._on_status_changed)
+        self._stats_dialog._shown_trigger.connect(self._on_stats_dialog_shown)
+        self._stats_dialog._status_changed_trigger.connect(self._on_stats_status_changed)
+        self._stats_dialog.settings_saved.connect(self._on_settings_saved)
+        self._stats_dialog.close_trigger.connect(self._on_close)
+        self._show_message_trigger.connect(self._show_systray_message)
+
+    def _setup_icons(self):
+        self.off_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-off.png"))
+        self.off_icon = QtGui.QIcon()
+        self.off_icon.addPixmap(self.off_image, QtGui.QIcon.Normal, QtGui.QIcon.Off)
+        self.white_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-white.svg"))
+        self.white_icon = QtGui.QIcon()
+        self.white_icon.addPixmap(self.white_image, QtGui.QIcon.Normal, QtGui.QIcon.Off)
+        self.red_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-red.png"))
+        self.red_icon = QtGui.QIcon()
+        self.red_icon.addPixmap(self.red_image, QtGui.QIcon.Normal, QtGui.QIcon.Off)
+        self.pause_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-pause.png"))
+        self.pause_icon = QtGui.QIcon()
+        self.pause_icon.addPixmap(self.pause_image, QtGui.QIcon.Normal, QtGui.QIcon.Off)
+        self.alert_image = QtGui.QPixmap(os.path.join(self._path, "res/icon-alert.png"))
+        self.alert_icon = QtGui.QIcon()
+        self.alert_icon.addPixmap(self.alert_image, QtGui.QIcon.Normal, QtGui.QIcon.Off)
+
+        self._app.setWindowIcon(self.white_icon)
+        # NOTE: only available since pyqt 5.7
+        if hasattr(self._app, "setDesktopFileName"):
+            self._app.setDesktopFileName(self.DESKTOP_FILENAME)
+
+    def _setup_tray(self):
+        self._tray = QtWidgets.QSystemTrayIcon(self.off_icon)
+        self._tray.show()
+
+        self._menu = QtWidgets.QMenu()
+        self._tray.setContextMenu(self._menu)
+        self._tray.activated.connect(self._on_tray_icon_activated)
+
+        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.addSeparator()
+        self._menu_autostart = self._menu.addAction("Autostart")
+        self._menu_autostart.setCheckable(True)
+        self._menu_autostart.setChecked(self._autostart.isEnabled())
+        self._menu_autostart.triggered.connect(self._on_switch_autostart)
+        self._menu.addSeparator()
+
+        self._menu.addAction(self.MENU_ENTRY_HELP).triggered.connect(
+                lambda: QtGui.QDesktopServices.openUrl(QtCore.QUrl(Config.HELP_CONFIG_URL))
+                )
+        self._menu.addAction(self.MENU_ENTRY_CLOSE).triggered.connect(self._on_close)
+
+        self._menu.aboutToShow.connect(self._on_show_menu)
+
+    def _on_switch_autostart(self):
+        try:
+            self._autostart.enable(self._menu_autostart.isChecked())
+        except Exception as e:
+            self._desktop_notifications.show(
+                QC.translate("stats", "Warning"),
+                QC.translate("stats", str(e))
+            )
+
+    def _on_show_menu(self):
+        self._menu_autostart.setChecked(self._autostart.isEnabled())
+
+    def _show_gui_if_tray_not_available(self):
+        """If the system tray is not available or ready, show the GUI after
+        10s. This delay helps to skip showing up the GUI when DEs' autologin is on.
+        """
+        tray = self._tray
+        gui = self._stats_dialog
+        def __show_gui():
+            if not tray.isSystemTrayAvailable():
+                self._show_systray_msg_error()
+                gui.show()
+
+        QtCore.QTimer.singleShot(10000, __show_gui)
+
+    def _show_systray_msg_error(self):
+        print("")
+        print("WARNING: system tray not available. On GNOME you need the extension gnome-shell-extension-appindicator.")
+        print("\tRead more:", Config.HELP_SYSTRAY_WARN)
+        print("\tIf you want to start OpenSnitch GUI in background even if tray not available, use --background argument.")
+        print("")
+
+        hide_msg = self._cfg.getBool(Config.DEFAULT_HIDE_SYSTRAY_WARN)
+        if hide_msg:
+            return
+        self._desktop_notifications.show(
+            QC.translate("stats", "WARNING"),
+            QC.translate("stats", """System tray not available. Read more:
+{0}
+""".format(Config.HELP_SYSTRAY_WARN)),
+            os.path.join(self._path, "res/icon-white.svg")
+        )
+        self._cfg.setSettings(Config.DEFAULT_HIDE_SYSTRAY_WARN, True)
+
+    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():
+                self._stats_dialog.hide()
+            elif self._stats_dialog.isVisible() and self._stats_dialog.isMinimized() and not self._stats_dialog.isMaximized():
+                self._stats_dialog.hide()
+                self._stats_dialog.showNormal()
+            elif self._stats_dialog.isVisible() and self._stats_dialog.isMinimized() and self._stats_dialog.isMaximized():
+                self._stats_dialog.hide()
+                self._stats_dialog.showMaximized()
+            else:
+                self._stats_dialog.show()
+
+    def _on_close(self):
+        self._exit = True
+        self._tray.setIcon(self.off_icon)
+        self._app.processEvents()
+        self._nodes.stop_notifications()
+        self._nodes.update_all(Nodes.OFFLINE)
+        self._db.vacuum()
+        self._db.optimize()
+        self._db.close()
+        self._stop_db_cleaner()
+        self._on_exit()
+
+    def _show_stats_dialog(self):
+        if self._connected and self._fw_enabled:
+            self._tray.setIcon(self.white_icon)
+        self._stats_dialog.show()
+
+    @QtCore.pyqtSlot(bool)
+    def _on_stats_status_changed(self, enabled):
+        self._update_fw_status(enabled)
+
+    @QtCore.pyqtSlot(bool)
+    def _on_status_changed(self, enabled):
+        self._set_daemon_connected(enabled)
+
+    @QtCore.pyqtSlot(str, str)
+    def _on_diff_versions(self, daemon_ver, ui_ver):
+        if self._version_warning_shown == False:
+            self._msg.setIcon(QtWidgets.QMessageBox.Warning)
+            self._msg.setWindowTitle("OpenSnitch version mismatch!")
+            self._msg.setText(("You are running version <b>%s</b> of the daemon, while the UI is at version " + \
+                              "<b>%s</b>, they might not be fully compatible.") % (daemon_ver, ui_ver))
+            self._msg.setStandardButtons(QtWidgets.QMessageBox.Ok)
+            self._msg.show()
+            self._version_warning_shown = True
+
+    @QtCore.pyqtSlot(str, str, ui_pb2.PingRequest)
+    def _on_update_stats(self, proto, addr, request):
+        main_need_refresh, details_need_refresh = self._populate_stats(self._db, proto, addr, request.stats)
+        is_local_request = self._is_local_request(proto, addr)
+        self._stats_dialog.update(is_local_request, request.stats, main_need_refresh or details_need_refresh)
+
+    @QtCore.pyqtSlot(str, str, ui_pb2.Alert)
+    def _on_new_alert(self, proto, addr, alert):
+        try:
+            is_local = self._is_local_request(proto, addr)
+
+            icon = QtWidgets.QSystemTrayIcon.Information
+            _title = QtCore.QCoreApplication.translate("messages", "Info")
+            atype = "INFO"
+            if alert.type == ui_pb2.Alert.ERROR:
+                atype = "ERROR"
+                _title = QtCore.QCoreApplication.translate("messages", "Error")
+                icon = QtWidgets.QSystemTrayIcon.Critical
+            if alert.type == ui_pb2.Alert.WARNING:
+                atype = "WARNING"
+                _title = QtCore.QCoreApplication.translate("messages", "Warning")
+                icon = QtWidgets.QSystemTrayIcon.Warning
+
+            body = ""
+            what = "GENERIC"
+            if alert.what == ui_pb2.Alert.GENERIC:
+                body = alert.text
+            elif alert.what == ui_pb2.Alert.KERNEL_EVENT:
+                body = "%s\n%s" % (alert.text, alert.proc.path)
+                what = "KERNEL EVENT"
+            if is_local is False:
+                body = "node: {0}:{1}\n\n{2}\n{3}".format(proto, addr, alert.text, alert.proc.path)
+
+            if alert.action == ui_pb2.Alert.SHOW_ALERT:
+
+                urgency = DesktopNotifications.URGENCY_NORMAL
+                if alert.priority == ui_pb2.Alert.LOW:
+                    urgency = DesktopNotifications.URGENCY_LOW
+                elif alert.priority == ui_pb2.Alert.HIGH:
+                    urgency = DesktopNotifications.URGENCY_CRITICAL
+
+                self._show_message_trigger.emit(_title, body, icon, urgency)
+
+            else:
+                print("PostAlert() unknown alert action:", alert.action)
+
+
+        except Exception as e:
+            print("PostAlert() exception:", e)
+            return ui_pb2.MsgResponse(id=1)
+
+    @QtCore.pyqtSlot(str, ui_pb2.PingRequest)
+    def _on_new_remote(self, addr, request):
+        self._remote_stats[addr] = {
+                'last_ping': datetime.now(),
+                'dialog': StatsDialog(address=addr, dbname=addr, db=self._db)
+                }
+        self._remote_stats[addr]['dialog'].daemon_connected = True
+        self._remote_stats[addr]['dialog'].update(addr, request.stats)
+        self._remote_stats[addr]['dialog'].show()
+
+    @QtCore.pyqtSlot()
+    def _on_stats_dialog_shown(self):
+        if self._connected:
+            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()
+
+    @QtCore.pyqtSlot(str, str, int, int)
+    def _show_systray_message(self, title, body, icon, urgency):
+        def callback_open_clicked(notifObject, action):
+            if action == DesktopNotifications.ACTION_ID_OPEN:
+                self._stats_dialog.show()
+                #self._stats_dialog.raise()
+                self._stats_dialog.activateWindow()
+
+        if self._desktop_notifications.are_enabled():
+            timeout = self._cfg.getInt(Config.DEFAULT_TIMEOUT_KEY, 15)
+
+            if self._desktop_notifications.is_available() and self._cfg.getInt(Config.NOTIFICATIONS_TYPE, 1) == Config.NOTIFICATION_TYPE_SYSTEM:
+                try:
+                    self._desktop_notifications.show(
+                        title,
+                        body,
+                        os.path.join(self._path, "res/icon-white.svg"),
+                        callback=callback_open_clicked
+                    )
+                except:
+                    self._tray.showMessage(title, body, icon, timeout * 1000)
+            else:
+                self._tray.showMessage(title, body, icon, timeout * 1000)
+
+        if icon == QtWidgets.QSystemTrayIcon.NoIcon:
+            self._tray.setIcon(self.alert_icon)
+
+    def _on_enable_interception_clicked(self):
+        self._enable_interception(self._fw_enabled)
+
+    @QtCore.pyqtSlot()
+    def _on_settings_saved(self):
+        if self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST):
+            if self._cleaner != None:
+                self._stop_db_cleaner()
+            self._start_db_cleaner()
+        elif self._cfg.getBool(Config.DEFAULT_DB_PURGE_OLDEST) == False and self._cleaner != None:
+            self._stop_db_cleaner()
+
+        theme_idx, theme_name, theme_density = self._themes.get_saved_theme()
+        if theme_idx > 0:
+            self._themes.load_theme(self._app)
+
+    def _init_translation(self):
+        if self.translator:
+            self._app.removeTranslator(self.translator)
+        saved_lang = self._cfg.getSettings(Config.DEFAULT_LANGUAGE)
+        self.translator = languages.init(saved_lang)
+        self._app.installTranslator(self.translator)
+
+    def _stop_db_cleaner(self):
+        if self._cleaner != None:
+            self._cleaner.stop()
+            self._cleaner = None
+
+    def _start_db_cleaner(self):
+        def _cleaner_task(db):
+            oldest = self._cfg.getInt(self._cfg.DEFAULT_DB_MAX_DAYS, 1)
+            db.purge_oldest(oldest)
+
+        interval = self._cfg.getInt(self._cfg.DEFAULT_DB_PURGE_INTERVAL, 5)
+        self._cleaner = CleanerTask(interval, _cleaner_task)
+        self._cleaner.start()
+
+    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.
+        """
+        self._fw_enabled = enabled
+        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)
+
+    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 _is_local_request(self, proto, addr):
+        if proto == "unix" or proto == "unix-abstract":
+            return True
+
+        elif proto == "ipv4" or proto == "ipv6":
+            for name, ip in self._interfaces.items():
+                if addr == ip:
+                    return True
+
+        return False
+
+    def _get_peer(self, peer):
+        """
+        server          -> client
+        127.0.0.1:50051 -> ipv4:127.0.0.1:52032
+        [::]:50051      -> ipv6:[::1]:59680
+        0.0.0.0:50051   -> ipv6:[::1]:59654
+        """
+        return self._nodes.get_addr(peer)
+
+    def _delete_node(self, peer):
+        try:
+            proto, addr = self._get_peer(peer)
+            if addr in self._last_stats:
+                del self._last_stats[addr]
+            for table in self._last_items:
+                if addr in self._last_items[table]:
+                    del self._last_items[table][addr]
+
+            self._nodes.update(peer, Nodes.OFFLINE)
+            self._nodes.delete(peer)
+            self._stats_dialog.update(True, None, True)
+        except Exception as e:
+            print("_delete_node() exception:", e)
+
+    def _populate_stats(self, db, proto, addr, stats):
+        main_need_refresh = False
+        details_need_refresh = False
+        try:
+            if db == None:
+                print("populate_stats() db None")
+                return main_need_refresh, details_need_refresh
+
+            peer = proto+":"+addr
+            _node = self._nodes.get_node(peer)
+            if _node == None:
+                return main_need_refresh, details_need_refresh
+
+            # TODO: move to nodes.add_node()
+            version  = _node['data'].version if _node != None else ""
+            hostname = _node['data'].name if _node != None else ""
+            db.insert("nodes",
+                    "(addr, status, hostname, daemon_version, daemon_uptime, " \
+                            "daemon_rules, cons, cons_dropped, version, last_connection)",
+                            (peer, Nodes.ONLINE, hostname, stats.daemon_version, str(timedelta(seconds=stats.uptime)),
+                            stats.rules, stats.connections, stats.dropped,
+                            version, datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
+
+            if addr not in self._last_stats:
+                self._last_stats[addr] = []
+
+            db.transaction()
+            for event in stats.events:
+                if event.unixnano in self._last_stats[addr]:
+                    continue
+                main_need_refresh=True
+                db.insert("connections",
+                        "(time, node, action, protocol, src_ip, src_port, dst_ip, dst_host, dst_port, uid, pid, process, process_args, process_cwd, rule)",
+                        (str(datetime.fromtimestamp(event.unixnano/1000000000)), peer, event.rule.action,
+                            event.connection.protocol, event.connection.src_ip, str(event.connection.src_port),
+                            event.connection.dst_ip, event.connection.dst_host, str(event.connection.dst_port),
+                            str(event.connection.user_id), str(event.connection.process_id),
+                            event.connection.process_path, " ".join(event.connection.process_args),
+                            event.connection.process_cwd, event.rule.name),
+                            action_on_conflict="IGNORE"
+                        )
+                self._nodes.update_rule_time(
+                    str(datetime.fromtimestamp(event.unixnano/1000000000)),
+                    event.rule.name,
+                    peer
+                )
+            db.commit()
+
+            details_need_refresh = self._populate_stats_details(db, addr, stats)
+            self._last_stats[addr] = []
+            for event in stats.events:
+                self._last_stats[addr].append(event.unixnano)
+        except Exception as e:
+            print("_populate_stats() exception: ", e)
+
+        return main_need_refresh, details_need_refresh
+
+    def _populate_stats_details(self, db, addr, stats):
+        need_refresh = False
+        changed = self._populate_stats_events(db, addr, stats, "hosts", ("what", "hits"), (1,2), stats.by_host.items())
+        if changed: need_refresh = True
+        changed = self._populate_stats_events(db, addr, stats, "procs", ("what", "hits"), (1,2), stats.by_executable.items())
+        if changed: need_refresh = True
+        changed = self._populate_stats_events(db, addr, stats, "addrs", ("what", "hits"), (1,2), stats.by_address.items())
+        if changed: need_refresh = True
+        changed = self._populate_stats_events(db, addr, stats, "ports", ("what", "hits"), (1,2), stats.by_port.items())
+        if changed: need_refresh = True
+        changed = self._populate_stats_events(db, addr, stats, "users", ("what", "hits"), (1,2), stats.by_uid.items())
+        if changed: need_refresh = True
+
+        return need_refresh
+
+    def _populate_stats_events(self, db, addr, stats, table, colnames, cols, items):
+        fields = []
+        values = []
+        need_refresh = False
+        try:
+            if addr not in self._last_items[table].keys():
+                self._last_items[table][addr] = {}
+            if items == self._last_items[table][addr]:
+                return need_refresh
+
+            for row, event in enumerate(items):
+                if event in self._last_items[table][addr]:
+                    continue
+                need_refresh = True
+                what, hits = event
+                # FIXME: this is suboptimal
+                # BUG: there can be users with same id on different machines but with different names
+                if table == "users":
+                    what = Utils.get_user_id(what)
+                fields.append(what)
+                values.append(int(hits))
+            # FIXME: default action on conflict is to replace. If there're multiple nodes connected,
+            # stats are painted once per node on each update.
+            if need_refresh:
+                db.insert_batch(table, colnames, cols, fields, values)
+
+            self._last_items[table][addr] = items
+        except Exception as e:
+            print("details exception: ", e)
+
+        return need_refresh
+
+    def _overwrite_nodes_config(self, node_config):
+        """Overwrite daemon's DefaultAction value, with the one defined by the GUI.
+        It'll only be valid while the daemon is connected to the GUI (it's not saved to disk).
+        """
+        newconf = copy.deepcopy(node_config)
+        _default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY)
+        try:
+            temp_cfg = json.loads(newconf.config)
+            if _default_action == Config.ACTION_ALLOW_IDX:
+                temp_cfg['DefaultAction'] = Config.ACTION_ALLOW
+            else:
+                temp_cfg['DefaultAction'] = Config.ACTION_DENY
+
+            print("Setting daemon DefaultAction to:", temp_cfg['DefaultAction'])
+
+            newconf.config = json.dumps(temp_cfg)
+        except Exception as e:
+            print("error parsing node's configuration:", e)
+            return node_config
+
+        return newconf
+
+    @QtCore.pyqtSlot(dict)
+    def _on_node_actions(self, kwargs):
+        if kwargs['action'] == self.NODE_ADD:
+            n, addr = self._nodes.add(kwargs['peer'], kwargs['node_config'])
+            if n != None:
+                self._nodes.add_fw_rules(
+                    addr,
+                    FwRules.to_dict(kwargs['node_config'].systemFirewall.SystemRules)
+                )
+                self._status_change_trigger.emit(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(kwargs['node_config'].isFirewallRunning)
+                else:
+                    self._update_fw_status(True)
+        elif kwargs['action'] == self.ADD_RULE:
+            rule = kwargs['rule']
+            proto, addr = self._get_peer(kwargs['peer'])
+            self._nodes.add_rule((datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
+                                 "{0}:{1}".format(proto, addr),
+                                 rule.name, rule.description, str(rule.enabled),
+                                 str(rule.precedence), str(rule.nolog), rule.action, rule.duration,
+                                 rule.operator.type, str(rule.operator.sensitive), rule.operator.operand,
+                                 rule.operator.data,
+                                 str(datetime.fromtimestamp(rule.created).strftime("%Y-%m-%d %H:%M:%S"))
+                                 )
+            if rule.operator.type == Config.RULE_TYPE_LIST:
+                # reset list operator data before sending it back to the
+                # daemon.
+                rule.operator.data = ""
+
+        elif kwargs['action'] == self.DELETE_RULE:
+            self._db.delete_rule(kwargs['name'], kwargs['addr'])
+
+        elif kwargs['action'] == self.NODE_DELETE:
+            self._delete_node(kwargs['peer'])
+
+    def OpenWindow(self):
+        self._stats_dialog.show()
+
+    def close(self):
+        self._on_close()
+
+    def PostAlert(self, alert, context):
+        proto, addr = self._get_peer(context.peer())
+        self._add_alert_trigger.emit(proto, addr, alert)
+        return ui_pb2.MsgResponse(id=0)
+
+    def Ping(self, request, context):
+        try:
+            self._last_ping = datetime.now()
+            if Utils.check_versions(request.stats.daemon_version):
+                self._version_warning_trigger.emit(request.stats.daemon_version, version)
+
+            proto, addr = self._get_peer(context.peer())
+            # do not update db here, do it on the main thread
+            self._update_stats_trigger.emit(proto, addr, request)
+            #else:
+            #    with self._remote_lock:
+            #        # XXX: disable this option for now
+            #        # opening several dialogs only updates one of them.
+            #        if addr not in self._remote_stats:
+            #            self._new_remote_trigger.emit(addr, request)
+            #        else:
+            #            self._populate_stats(self._remote_stats[addr]['dialog'].get_db(), proto, addr, request.stats)
+            #            self._remote_stats[addr]['dialog'].update(addr, request.stats)
+
+        except Exception as e:
+            print("Ping exception: ", e)
+
+        return ui_pb2.PingReply(id=request.id)
+
+    def AskRule(self, request, context):
+        #def callback(ntf, action, connection):
+        # TODO
+
+        #if self._desktop_notifications.support_actions():
+        #    self._desktop_notifications.ask(request, callback)
+
+        # TODO: allow connections originated from ourselves: os.getpid() == request.pid)
+        self._asking = True
+        peer = context.peer()
+        proto, addr = self._get_peer(peer)
+        rule, timeout_triggered = self._prompt_dialog.promptUser(request, self._is_local_request(proto, addr), peer)
+        self._last_ping = datetime.now()
+        self._asking = False
+        if rule == None:
+            return None
+
+        if timeout_triggered:
+            _title = request.process_path
+            if _title == "":
+                _title = "%s:%d (%s)" % (request.dst_host if request.dst_host != "" else request.dst_ip, request.dst_port, request.protocol)
+
+
+            node_text = "" if self._is_local_request(proto, addr) else "on node {0}:{1}".format(proto, addr)
+            self._show_message_trigger.emit(_title,
+                                            "{0} action applied {1}\nCommand line: {2}"
+                                            .format(rule.action, node_text, " ".join(request.process_args)),
+                                            QtWidgets.QSystemTrayIcon.NoIcon,
+                                            DesktopNotifications.URGENCY_NORMAL)
+
+
+        if rule.duration in Config.RULES_DURATION_FILTER:
+            self._node_actions_trigger.emit(
+                {
+                    'action': self.DELETE_RULE,
+                    'name': rule.name,
+                    'addr': peer
+                }
+            )
+        else:
+            self._node_actions_trigger.emit(
+                {
+                    'action': self.ADD_RULE,
+                    'peer': peer,
+                    'rule': rule
+                }
+            )
+
+        return rule
+
+    def Subscribe(self, node_config, context):
+        """
+        Accept and collect nodes. It keeps a connection open with each
+        client, in order to send them notifications.
+
+        @doc: https://grpc.github.io/grpc/python/grpc.html#service-side-context
+        """
+        # if the exit mark is set, don't accept new connections.
+        # db vacuum operation may take a lot of time to complete.
+        if self._exit:
+            context.cancel()
+            return
+        try:
+            self._node_actions_trigger.emit({
+                    'action': self.NODE_ADD,
+                    'peer': context.peer(),
+                    'node_config': node_config
+                 })
+            # force events processing, to add the node ^ before the
+            # Notifications() call arrives.
+            self._app.processEvents()
+
+            proto, addr = self._get_peer(context.peer())
+            if self._is_local_request(proto, addr) == False:
+                self._show_message_trigger.emit(
+                    QtCore.QCoreApplication.translate("stats", "New node connected"),
+                    "({0})".format(context.peer()),
+                    QtWidgets.QSystemTrayIcon.Information,
+                    DesktopNotifications.URGENCY_LOW
+                )
+        except Exception as e:
+            print("[Notifications] exception adding new node:", e)
+            context.cancel()
+
+        newconf = self._overwrite_nodes_config(node_config)
+
+        return newconf
+
+    def Notifications(self, node_iter, context):
+        """
+        Accept and collect nodes. It keeps a connection open with each
+        client, in order to send them notifications.
+
+        @doc: https://grpc.github.io/grpc/python/grpc.html#service-side-context
+        @doc: https://grpc.io/docs/what-is-grpc/core-concepts/
+        """
+        proto, addr = self._get_peer(context.peer())
+        _node = self._nodes.get_node("%s:%s" % (proto, addr))
+        if _node == None:
+            return
+
+        stop_event = Event()
+        def _on_client_closed():
+            stop_event.set()
+            self._node_actions_trigger.emit(
+                {'action': self.NODE_DELETE,
+                 'peer': context.peer(),
+                 })
+
+            self._status_change_trigger.emit(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._show_message_trigger.emit("node exited",
+                                                "({0})".format(context.peer()),
+                                                QtWidgets.QSystemTrayIcon.Information,
+                                                DesktopNotifications.URGENCY_LOW)
+
+        context.add_callback(_on_client_closed)
+
+        # TODO: move to notifications.py
+        def new_node_message():
+            print("new node connected, listening for client responses...", addr)
+
+            while self._exit == False:
+                try:
+                    if stop_event.is_set():
+                        break
+                    in_message = next(node_iter)
+                    if in_message == None:
+                        continue
+
+                    self._nodes.reply_notification(addr, in_message)
+                except StopIteration:
+                    print("[Notifications] Node {0} exited".format(addr))
+                    break
+                except grpc.RpcError as e:
+                    print("[Notifications] grpc exception new_node_message(): ", addr, in_message)
+                except Exception as e:
+                    print("[Notifications] unexpected exception new_node_message(): ", addr, e, in_message)
+
+        read_thread = Thread(target=new_node_message)
+        read_thread.daemon = True
+        read_thread.start()
+
+        while self._exit == False:
+            if stop_event.is_set():
+                break
+
+            try:
+                noti = _node['notifications'].get()
+                if noti != None:
+                    _node['notifications'].task_done()
+                    yield noti
+            except Exception as e:
+                print("[Notifications] exception getting notification from queue:", addr, e)
+                context.cancel()
+
+        return node_iter
diff --git a/ui/opensnitch/utils/__init__.py b/ui/opensnitch/utils/__init__.py
new file mode 100644 (file)
index 0000000..4d81e48
--- /dev/null
@@ -0,0 +1,507 @@
+
+from PyQt5 import QtCore, QtWidgets, QtGui
+from opensnitch.version import version as gui_version
+from opensnitch.database import Database
+from opensnitch.config import Config
+from threading import Thread, Event
+import pwd
+import socket
+import fcntl
+import struct
+import array
+import os, sys, glob
+import enum
+import re
+
+class AsnDB():
+    __instance = None
+    asndb = None
+
+    @staticmethod
+    def instance():
+        if AsnDB.__instance == None:
+            AsnDB.__instance = AsnDB()
+        return AsnDB.__instance
+
+    def __init__(self):
+        self.ASN_AVAILABLE = True
+        self.load()
+
+    def is_available(self):
+        return self.ASN_AVAILABLE
+
+    def load(self):
+        """Load the ASN DB from disk.
+
+        It'll try to load it from user's opensnitch directory if these file exist:
+            - ~/.config/opensnitch/ipasn_db.dat.gz
+            - ~/.config/opensnitch/asnames.json
+        Otherwise it'll try to load it from python3-pyasn package.
+        """
+        try:
+            if self.asndb != None:
+                return
+
+            import pyasn
+
+            IPASN_DB_PATH = os.path.expanduser('~/.config/opensnitch/ipasn_db.dat.gz')
+            # .gz not supported for asnames
+            AS_NAMES_FILE_PATH = os.path.expanduser('~/.config/opensnitch/asnames.json')
+
+            # if the user hasn't downloaded an updated ipasn db, use the one
+            # shipped with the python3-pyasn package
+            if os.path.isfile(IPASN_DB_PATH) == False:
+                IPASN_DB_PATH = '/usr/lib/python3/dist-packages/data/ipasn_20140513_v12.dat.gz'
+            if os.path.isfile(AS_NAMES_FILE_PATH) == False:
+                AS_NAMES_FILE_PATH = '/usr/lib/python3/dist-packages/data/asnames.json'
+
+            print("using IPASN DB:", IPASN_DB_PATH)
+            self.asndb = pyasn.pyasn(IPASN_DB_PATH, as_names_file=AS_NAMES_FILE_PATH)
+        except Exception as e:
+            self.ASN_AVAILABLE = False
+            print("exception loading ipasn db:", e)
+            print("Install python3-pyasn to display IP's network name.")
+
+
+    def lookup(self, ip):
+        """Lookup the IP in the ASN DB.
+
+        Return the net range and the prefix if found, otherwise nothing.
+        """
+        try:
+            return self.asndb.lookup(ip)
+        except Exception:
+            return "", ""
+
+    def get_as_name(self, asn):
+        """Get the ASN name given a network range.
+
+        Return the name of the network if found, otherwise nothing.
+        """
+        try:
+            asname = self.asndb.get_as_name(asn)
+            if asname == None:
+                asname = ""
+            return asname
+        except Exception:
+            return ""
+
+    def get_asn(self, ip):
+        try:
+            asn, prefix = self.lookup(ip)
+            return self.get_as_name(asn)
+        except Exception:
+            return ""
+
+class Themes():
+    """Change GUI's appearance using qt-material lib.
+    https://github.com/UN-GCPDS/qt-material
+    """
+    THEMES_PATH = [
+        os.path.expanduser("~/.config/opensnitch/"),
+        os.path.dirname(sys.modules[__name__].__file__)
+    ]
+    __instance = None
+
+    AVAILABLE = False
+    try:
+        from qt_material import apply_stylesheet as qtmaterial_apply_stylesheet
+        from qt_material import list_themes as qtmaterial_themes
+        AVAILABLE = True
+    except Exception:
+        print("Themes not available. Install qt-material if you want to change GUI's appearance: pip3 install qt-material.")
+
+    @staticmethod
+    def instance():
+        if Themes.__instance == None:
+            Themes.__instance = Themes()
+        return Themes.__instance
+
+    def __init__(self):
+        self._cfg = Config.get()
+        theme = self._cfg.getInt(self._cfg.DEFAULT_THEME, 0)
+
+    def available(self):
+        return Themes.AVAILABLE
+
+    def get_saved_theme(self):
+        theme = self._cfg.getSettings(self._cfg.DEFAULT_THEME)
+        theme_density = self._cfg.getSettings(self._cfg.DEFAULT_THEME_DENSITY_SCALE)
+        if theme_density == "" or theme_density == None:
+            theme_density = '0'
+
+        if not Themes.AVAILABLE:
+            return 0, "", theme_density
+
+        if theme != "" and theme != None:
+            # 0 == System
+            return self.list_themes().index(theme)+1, theme, theme_density
+        return 0, "", theme_density
+
+    def save_theme(self, theme_idx, theme, density_scale):
+        if not Themes.AVAILABLE:
+            return
+
+        self._cfg.setSettings(self._cfg.DEFAULT_THEME_DENSITY_SCALE, density_scale)
+        if theme_idx == 0:
+            self._cfg.setSettings(self._cfg.DEFAULT_THEME, "")
+        else:
+            self._cfg.setSettings(self._cfg.DEFAULT_THEME, theme)
+
+    def load_theme(self, app):
+        if not Themes.AVAILABLE:
+            return
+
+        try:
+            theme_idx, theme_name, theme_density = self.get_saved_theme()
+            if theme_name != "":
+                invert = "light" in theme_name
+                print("Using theme:", theme_idx, theme_name, "inverted:", invert)
+                # TODO: load {theme}.xml.extra and .xml.css for further
+                # customizations.
+                extra_opts = {
+                    'density_scale': theme_density
+                }
+                Themes.qtmaterial_apply_stylesheet(app, theme=theme_name,  invert_secondary=invert, extra=extra_opts)
+        except Exception as e:
+            print("Themes.load_theme() exception:", e)
+
+    def change_theme(self, window, theme_name, extra={}):
+        try:
+            invert = "light" in theme_name
+            Themes.qtmaterial_apply_stylesheet(window, theme=theme_name,  invert_secondary=invert, extra=extra)
+        except Exception as e:
+            print("Themes.change_theme() exception:", e, " - ", window, theme_name)
+
+    def list_local_themes(self):
+        themes = []
+        if not Themes.AVAILABLE:
+            return themes
+
+        try:
+            for tdir in self.THEMES_PATH:
+                themes += glob.glob(tdir + "/themes/*.xml")
+        except Exception:
+            pass
+        finally:
+            return themes
+
+    def list_themes(self):
+        themes = self.list_local_themes()
+        if not Themes.AVAILABLE:
+            return themes
+
+        themes += Themes.qtmaterial_themes()
+        return themes
+
+class GenericTimer(Thread):
+    interval = 1
+    stop_flag = None
+    callback = None
+
+    def __init__(self, _interval, _callback, _args=()):
+        Thread.__init__(self, name="generic_timer_thread")
+        self.interval = _interval
+        self.stop_flag = Event()
+        self.callback = _callback
+        self.args = _args
+
+    def run(self):
+        while self.stop_flag.wait(self.interval):
+            if self.stop_flag.is_set():
+                self.callback(self.args)
+                break
+
+    def stop(self):
+        self.stop_flag.set()
+
+class OneshotTimer(GenericTimer):
+    def __init__(self, _interval, _callback, _args=()):
+        GenericTimer.__init__(self, _interval, _callback, _args)
+
+    def run(self):
+        self.stop_flag.wait(self.interval)
+        self.callback(self.args)
+
+class CleanerTask(Thread):
+    interval = 1
+    stop_flag = None
+    callback = None
+
+    def __init__(self, _interval, _callback):
+        Thread.__init__(self, name="cleaner_db_thread")
+        self.interval = _interval * 60
+        self.stop_flag = Event()
+        self.callback = _callback
+        self._cfg = Config.init()
+
+        # We need to instantiate a new QsqlDatabase object with a unique name,
+        # because it's not thread safe:
+        # "A connection can only be used from within the thread that created it."
+        # https://doc.qt.io/qt-5/threads-modules.html#threads-and-the-sql-module
+        # The filename and type is the same, the one chosen by the user.
+        self.db = Database("db-cleaner-connection")
+        self.db_status, db_error = self.db.initialize(
+            dbtype=self._cfg.getInt(self._cfg.DEFAULT_DB_TYPE_KEY),
+            dbfile=self._cfg.getSettings(self._cfg.DEFAULT_DB_FILE_KEY),
+            dbjrnl_wal=self._cfg.getBool(self._cfg.DEFAULT_DB_JRNL_WAL)
+        )
+
+    def run(self):
+        if self.db_status == False:
+            return
+        while not self.stop_flag.is_set():
+            self.stop_flag.wait(self.interval)
+            self.callback(self.db)
+
+    def stop(self):
+        self.stop_flag.set()
+        self.db.close()
+
+class QuickHelp():
+    @staticmethod
+    def show(help_str):
+        QtWidgets.QToolTip.showText(QtGui.QCursor.pos(), help_str)
+
+class Utils():
+    @staticmethod
+    def check_versions(daemon_version):
+        lMayor, lMinor, lPatch = gui_version.split(".", 2)
+        rMayor, rMinor, rPatch = daemon_version.split(".", 2)
+        return lMayor != rMayor or (lMayor == rMayor and lMinor != rMinor)
+
+    @staticmethod
+    def get_user_id(uid):
+        pw_name = uid
+        try:
+            pw_name = pwd.getpwuid(int(uid)).pw_name + " (" + uid + ")"
+        except Exception:
+            #pw_name += " (error)"
+            pass
+
+        return pw_name
+
+    @staticmethod
+    def get_interfaces():
+        max_possible = 128  # arbitrary. raise if needed.
+        bytes = max_possible * 32
+        s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+        names = array.array('B', b'\0' * bytes)
+        outbytes = struct.unpack('iL', fcntl.ioctl(
+            s.fileno(),
+            0x8912,  # SIOCGIFCONF
+            struct.pack('iL', bytes, names.buffer_info()[0])
+        ))[0]
+        return names.tobytes(), outbytes
+
+    @staticmethod
+    def create_socket_dirs():
+        """https://www.linuxbase.org/betaspecs/fhs/fhs.html#runRuntimeVariableData
+        """
+        run_path = "/run/user/{0}".format(os.getuid())
+        var_run_path = "/var{0}".format(run_path)
+
+        try:
+            if os.path.exists(run_path):
+                os.makedirs(run_path + "/opensnitch/", 0o700)
+            if os.path.exists(var_run_path):
+                os.makedirs(var_run_path + "/opensnitch/", 0o700)
+        except:
+            pass
+
+class Message():
+
+    @staticmethod
+    def ok(title, message, icon):
+        msgBox = QtWidgets.QMessageBox()
+        msgBox.setWindowFlags(msgBox.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
+        msgBox.setText("<b>{0}</b><br><br>{1}".format(title, message))
+        msgBox.setIcon(icon)
+        msgBox.setModal(True)
+        msgBox.setStandardButtons(QtWidgets.QMessageBox.Ok)
+        msgBox.exec_()
+
+    @staticmethod
+    def yes_no(title, message, icon):
+        msgBox = QtWidgets.QMessageBox()
+        msgBox.setWindowFlags(msgBox.windowFlags() | QtCore.Qt.WindowStaysOnTopHint)
+        msgBox.setText(title)
+        msgBox.setIcon(icon)
+        msgBox.setModal(True)
+        msgBox.setInformativeText(message)
+        msgBox.setStandardButtons(QtWidgets.QMessageBox.Cancel | QtWidgets.QMessageBox.Yes)
+        msgBox.setDefaultButton(QtWidgets.QMessageBox.Cancel)
+        return msgBox.exec_()
+
+class FileDialog():
+
+    @staticmethod
+    def save(parent):
+        options = QtWidgets.QFileDialog.Options()
+        fileName, _ = QtWidgets.QFileDialog.getSaveFileName(parent, "", "","All Files (*)", options=options)
+        return fileName
+
+    @staticmethod
+    def select(parent):
+        options = QtWidgets.QFileDialog.Options()
+        fileName, _ = QtWidgets.QFileDialog.getOpenFileName(parent, "", "","All Files (*)", options=options)
+        return fileName
+
+    @staticmethod
+    def select_dir(parent, current_dir):
+        options = QtWidgets.QFileDialog.Options()
+        fileName = QtWidgets.QFileDialog.getExistingDirectory(parent, "", current_dir, options)
+        return fileName
+
+# https://stackoverflow.com/questions/29503339/how-to-get-all-values-from-python-enum-class
+class Enums(enum.Enum):
+    @classmethod
+    def to_dict(cls):
+        return {e.name: e.value for e in cls}
+
+    @classmethod
+    def keys(cls):
+        return cls._member_names_
+
+    @classmethod
+    def values(cls):
+        return [str(v.value) for v in cls]
+
+class NetworkInterfaces():
+    # https://gist.github.com/pklaus/289646
+    @staticmethod
+    def list():
+        namestr, outbytes = Utils.get_interfaces()
+        _interfaces = {}
+        for i in range(0, outbytes, 40):
+            try:
+                name = namestr[i:i+16].split(b'\0', 1)[0]
+                addr = namestr[i+20:i+24]
+                _interfaces[name.decode()] = "%d.%d.%d.%d" % (int(addr[0]), int(addr[1]), int(addr[2]), int(addr[3]))
+            except Exception as e:
+                print("utils.NetworkInterfaces() exception:", e)
+
+        return _interfaces
+
+
+
+class NetworkServices():
+    """Get a list of known ports. /etc/services
+    """
+    __instance = None
+
+    @staticmethod
+    def instance():
+        if NetworkServices.__instance == None:
+            NetworkServices.__instance = NetworkServices()
+        return NetworkServices.__instance
+
+    srv_array = []
+    ports_list = []
+
+    def __init__(self):
+        etcServicesPath = "/etc/services"
+        if not os.path.isfile(etcServicesPath) and os.path.isfile("/usr/etc/services"):
+            etcServicesPath = "/usr/etc/services"
+
+        try:
+            etcServices = open(etcServicesPath)
+            for line in etcServices:
+                if line[0] == "#":
+                    continue
+                g = re.search(r'([a-zA-Z0-9\-]+)( |\t)+([0-9]+)\/([a-zA-Z0-9\-]+)(.*)\n', line)
+                if g:
+                    self.srv_array.append("{0}/{1} {2}".format(
+                        g.group(1),
+                        g.group(3),
+                        "" if len(g.groups())>3 and g.group(4) == "" else "({0})".format(g.group(4).replace("\t", ""))
+                    )
+                    )
+                    self.ports_list.append(g.group(3))
+
+            # extra ports that don't exist in /etc/services
+            self.srv_array.append("wireguard/51820 WireGuard VPN")
+            self.ports_list.append("51820")
+        except Exception as e:
+            print("Error loading {0}: {1}".format(etcServicesPath, e))
+
+    def to_array(self):
+        return self.srv_array
+
+    def service_by_index(self, idx):
+        return self.srv_array[idx]
+
+    def service_by_name(self, name):
+        return self.srv_array.index(name)
+
+    def port_by_index(self, idx):
+        return self.ports_list[idx]
+
+    def index_by_port(self, port):
+        return self.ports_list.index(str(port))
+
+class Icons():
+    """Util to display Qt's built-in icons when the system is not configured as
+    we expect. More information:
+        https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems#no-icons-on-the-gui
+        https://www.pythonguis.com/faq/built-in-qicons-pyqt/icons-builtin.png
+    """
+
+    defaults = {
+        'document-new': "SP_FileIcon",
+        'document-save': "SP_DialogSaveButton",
+        'document-open': "SP_DirOpenIcon",
+        'format-justify-fill': "SP_FileDialogDetailedView",
+        'preferences-system': "SP_FileDialogListView",
+        'preferences-desktop': "SP_FileDialogListView",
+        'security-high': "SP_VistaShield",
+        'go-previous': "SP_ArrowLeft",
+        'go-jump': "SP_CommandLink",
+        'go-down': "SP_TitleBarUnshadeButton",
+        'go-up': "SP_TitleBarShadeButton",
+        'help-browser': "SP_DialogHelpButton",
+        'emblem-important': "SP_DialogCancelButton",
+        'emblem-default': "SP_DialogApplyButton",
+        'window-close': "SP_DialogCloseButton",
+        'system-run': "",
+        'preferences-system-network': "",
+        'document-properties': "",
+        'edit-delete': "SP_DialogCancelButton",
+        'list-add': "SP_ArrowUp",
+        'list-remove': "SP_ArrowDown",
+        'system-search': "SP_FileDialogContentsView",
+        'application-exit': "SP_TitleBarCloseButton",
+        'view-sort-ascending': "SP_ToolBarVerticalExtensionButton",
+        'address-book-new': "",
+        'media-playback-start': "SP_MediaPlay",
+        'media-playback-pause': "SP_MediaPause",
+        'system-search': "SP_FileDialogContentsView",
+        'accessories-text-editor': "SP_DialogOpenButton",
+        'edit-clear-all': "SP_DialogResetButton",
+        'reload': "SP_DialogResetButton",
+        'dialog-information': "SP_MessageBoxInformation",
+        'dialog-warning': "SP_MessageBoxWarning"
+    }
+
+    @staticmethod
+    def new(widget, icon_name):
+        icon = QtGui.QIcon.fromTheme(icon_name, QtGui.QIcon.fromTheme(icon_name + "-symbolic"))
+        if icon.isNull():
+            try:
+                return widget.style().standardIcon(getattr(QtWidgets.QStyle, Icons.defaults[icon_name]))
+            except Exception as e:
+                print("Qt standardIcon exception:", icon_name, ",", e)
+
+        return icon
+
+class Versions():
+    @staticmethod
+    def get():
+        try:
+            from google.protobuf import __version__ as proto_version
+            from grpc import _grpcio_metadata as grpcmeta
+
+            return gui_version, grpcmeta.__version__, proto_version
+
+        except:
+            return "none", "none", "none"
diff --git a/ui/opensnitch/utils/infowindow.py b/ui/opensnitch/utils/infowindow.py
new file mode 100644 (file)
index 0000000..478ec6e
--- /dev/null
@@ -0,0 +1,54 @@
+
+from opensnitch.config import Config
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+class InfoWindow(QtWidgets.QDialog):
+    """Display a text on a small dialog.
+    """
+    def __init__(self, parent):
+        QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.Tool)
+        self.setContentsMargins(0, 0, 0, 0)
+
+        self._cfg = Config.get()
+
+        self.layout = QtWidgets.QVBoxLayout(self)
+        self._textedit = QtWidgets.QTextEdit()
+        # hide cursor
+        self._textedit.setCursorWidth(0)
+        self._textedit.setMinimumSize(300, 325)
+        self._textedit.setReadOnly(True)
+        self._textedit.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.TextSelectableByKeyboard)
+
+        self.layout.addWidget(self._textedit)
+
+        self._load_settings()
+
+    def closeEvent(self, ev):
+        self._save_settings()
+        ev.accept()
+        self.hide()
+
+    def _load_settings(self):
+        saved_geometry = self._cfg.getSettings(Config.INFOWIN_GEOMETRY)
+        if saved_geometry is not None:
+            self.restoreGeometry(saved_geometry)
+
+    def _save_settings(self):
+        self._cfg.setSettings(Config.INFOWIN_GEOMETRY, self.saveGeometry())
+
+    def showText(self, text):
+        self._load_settings()
+
+        self._textedit.setText(text)
+
+        pos = QtGui.QCursor.pos()
+        win_size = self.size()
+        # center dialog on cursor, relative to the parent widget.
+        x_off = (int(win_size.width()/2))
+        y_off = (int(win_size.height()/2))
+        point = QtCore.QPoint(
+            pos.x()-x_off, pos.y()-y_off
+        )
+        self.move(point.x(), point.y())
+
+        self.show()
diff --git a/ui/opensnitch/utils/languages.py b/ui/opensnitch/utils/languages.py
new file mode 100644 (file)
index 0000000..90f4a78
--- /dev/null
@@ -0,0 +1,36 @@
+from PyQt5 import QtCore
+import os
+
+from opensnitch.config import Config
+
+def __get_i18n_path():
+    return os.path.dirname(os.path.realpath(__file__)) + "/../i18n"
+
+def init(saved_lang):
+    locale = QtCore.QLocale.system()
+    lang = locale.name()
+    if saved_lang:
+        lang = saved_lang
+    i18n_path = __get_i18n_path()
+    print("Loading translations:", i18n_path, "locale:", lang)
+    translator = QtCore.QTranslator()
+    translator.load(i18n_path + "/" + lang + "/opensnitch-" + lang + ".qm")
+
+    return translator
+
+def save(cfg, lang):
+    q = QtCore.QLocale(lang)
+    cfg.setSettings(Config.DEFAULT_LANGUAGE, lang)
+    cfg.setSettings(Config.DEFAULT_LANGNAME, q.nativeLanguageName().capitalize())
+
+def get_all():
+    langs = []
+    names = []
+    i18n_path = __get_i18n_path()
+    lang_dirs = os.listdir(i18n_path)
+    lang_dirs.sort()
+    for lang in lang_dirs:
+        q = QtCore.QLocale(lang)
+        langs.append(lang)
+        names.append(q.nativeLanguageName())
+    return langs, names
diff --git a/ui/opensnitch/utils/qvalidator.py b/ui/opensnitch/utils/qvalidator.py
new file mode 100644 (file)
index 0000000..881d4e6
--- /dev/null
@@ -0,0 +1,25 @@
+
+from PyQt5 import QtCore, QtGui
+
+class RestrictChars(QtGui.QValidator):
+    result = QtCore.pyqtSignal(object)
+
+    def __init__(self, restricted_chars, *args, **kwargs):
+        QtGui.QValidator.__init__(self, *args, **kwargs)
+        self._restricted_chars = restricted_chars
+
+    def validate(self, value, pos):
+        # allow to delete all characters
+        if len(value) == 0:
+            return QtGui.QValidator.Intermediate, value, pos
+
+        # user can type characters or paste them.
+        # pos value when pasting can be any number, depending on where did the
+        # user paste the characters.
+        for char in self._restricted_chars:
+            if char in value:
+                self.result.emit(QtGui.QValidator.Invalid)
+                return QtGui.QValidator.Invalid, value, pos
+
+        self.result.emit(QtGui.QValidator.Acceptable)
+        return QtGui.QValidator.Acceptable, value, pos
diff --git a/ui/opensnitch/utils/xdg.py b/ui/opensnitch/utils/xdg.py
new file mode 100644 (file)
index 0000000..6158f37
--- /dev/null
@@ -0,0 +1,117 @@
+
+import os
+import re
+import shutil
+import stat
+
+# https://github.com/takluyver/pyxdg/blob/1d23e483ae869ee9532aca43b133cc43f63626a3/xdg/BaseDirectory.py
+def get_runtime_dir(strict=True):
+    try:
+        return os.environ['XDG_RUNTIME_DIR']
+    except KeyError:
+        if strict:
+            raise
+
+        import getpass
+        fallback = '/tmp/opensnitch-' + getpass.getuser()
+        create = False
+
+        try:
+            # This must be a real directory, not a symlink, so attackers can't
+            # point it elsewhere. So we use lstat to check it.
+            st = os.lstat(fallback)
+        except OSError as e:
+            import errno
+            if e.errno == errno.ENOENT:
+                create = True
+            else:
+                raise
+        else:
+            # The fallback must be a directory
+            if not stat.S_ISDIR(st.st_mode):
+                os.unlink(fallback)
+                create = True
+            # Must be owned by the user and not accessible by anyone else
+            elif (st.st_uid != os.getuid()) \
+              or (st.st_mode & (stat.S_IRWXG | stat.S_IRWXO)):
+                os.rmdir(fallback)
+                create = True
+
+        if create:
+            os.mkdir(fallback, 0o700)
+
+        return fallback
+
+def get_run_opensnitch_dir():
+    rdir = get_runtime_dir(False)
+    if 'opensnitch' not in rdir:
+        rdir = os.path.join(rdir, 'opensnitch')
+        try:
+            os.makedirs(rdir, 0o700)
+        except:
+            pass
+
+    return rdir
+
+
+class Autostart():
+    def __init__(self):
+        desktopFile = 'opensnitch_ui.desktop'
+        self.systemDesktop = os.path.join('/usr/share/applications', desktopFile)
+        self.systemAutostart = os.path.join('/etc/xdg/autostart', desktopFile)
+        if not os.path.isfile(self.systemAutostart) and os.path.isfile('/usr' + self.systemAutostart):
+            self.systemAutostart = '/usr' + self.systemAutostart
+        self.userAutostart = os.path.join(xdg_config_home, 'autostart', desktopFile)
+
+    def _copyfile(self, src, dst):
+        """copy file (.desktop) to dst. Ignore exception if src and dst are equal"""
+        try:
+            shutil.copyfile(src, dst)
+        except shutil.SameFileError:
+            pass
+
+    def createUserDir(self):
+        if not os.path.isdir(xdg_config_home):
+            os.makedirs(xdg_config_home, 0o700)
+        if not os.path.isdir(os.path.dirname(self.userAutostart)):
+            os.makedirs(os.path.dirname(self.userAutostart), 0o755)
+
+    def isEnabled(self):
+        ret = False
+        if os.path.isfile(self.userAutostart):
+             ret = True
+             lines = open(self.userAutostart, 'r').readlines()
+             for line in lines:
+                 if re.search("^Hidden=true", line, re.IGNORECASE):
+                     ret = False
+                     break
+        elif os.path.isfile(self.systemAutostart):
+            ret = True
+        return ret
+
+    def enable(self, mode=True):
+        self.createUserDir()
+        if mode == True:
+            if os.path.isfile(self.systemAutostart) and os.path.isfile(self.userAutostart):
+                os.remove(self.userAutostart)
+            elif os.path.isfile(self.systemDesktop):
+                self._copyfile(self.systemDesktop, self.userAutostart)
+        else:
+            if os.path.isfile(self.systemAutostart):
+                self._copyfile(self.systemAutostart, self.userAutostart)
+                with open(self.userAutostart, 'a') as f:
+                    f.write('Hidden=true\n')
+            elif os.path.isfile(self.userAutostart):
+                os.remove(self.userAutostart)
+
+    def disable(self):
+        self.enable(False)
+
+
+_home = os.path.expanduser('~')
+xdg_config_home = os.environ.get('XDG_CONFIG_HOME') or os.path.join(_home, '.config')
+xdg_runtime_dir = get_runtime_dir(False)
+xdg_current_desktop = os.environ.get('XDG_CURRENT_DESKTOP')
+xdg_current_session = os.environ.get('XDG_SESSION_TYPE')
+
+xdg_opensnitch_dir = get_run_opensnitch_dir()
diff --git a/ui/opensnitch/version.py b/ui/opensnitch/version.py
new file mode 100644 (file)
index 0000000..c042c67
--- /dev/null
@@ -0,0 +1 @@
+version = '1.6.9'
diff --git a/ui/requirements.txt b/ui/requirements.txt
new file mode 100644 (file)
index 0000000..66e0de1
--- /dev/null
@@ -0,0 +1,5 @@
+grpcio-tools>=1.10.1
+pyinotify==0.9.6
+unicode_slugify==0.1.5
+pyqt5>=5.6
+protobuf
diff --git a/ui/resources/icons/48x48/opensnitch-ui.png b/ui/resources/icons/48x48/opensnitch-ui.png
new file mode 100644 (file)
index 0000000..6dcbd5f
Binary files /dev/null and b/ui/resources/icons/48x48/opensnitch-ui.png differ
diff --git a/ui/resources/icons/64x64/opensnitch-ui.png b/ui/resources/icons/64x64/opensnitch-ui.png
new file mode 100644 (file)
index 0000000..4b73e6b
Binary files /dev/null and b/ui/resources/icons/64x64/opensnitch-ui.png differ
diff --git a/ui/resources/icons/opensnitch-ui.svg b/ui/resources/icons/opensnitch-ui.svg
new file mode 100644 (file)
index 0000000..df12789
--- /dev/null
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="64"
+   height="64"
+   viewBox="0 0 12.698413 12.698413"
+   version="1.1"
+   id="svg8"
+   inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
+   sodipodi:docname="opensnitch-ui.svg"
+   inkscape:export-filename="64x64/opensnitch-ui.png"
+   inkscape:export-xdpi="96"
+   inkscape:export-ydpi="96"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:dc="http://purl.org/dc/elements/1.1/">
+  <defs
+     id="defs2" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="9.75"
+     inkscape:cx="21.74359"
+     inkscape:cy="31.794872"
+     inkscape:document-units="px"
+     inkscape:current-layer="g4133"
+     showgrid="true"
+     inkscape:window-width="1600"
+     inkscape:window-height="839"
+     inkscape:window-x="0"
+     inkscape:window-y="24"
+     inkscape:window-maximized="1"
+     units="px"
+     inkscape:pagecheckerboard="0"
+     inkscape:showpageshadow="2"
+     inkscape:deskcolor="#d1d1d1"
+     showguides="true">
+    <sodipodi:guide
+       position="13.436869,10.958536"
+       orientation="0,-1"
+       id="guide291"
+       inkscape:locked="false" />
+    <sodipodi:guide
+       position="13.233163,1.6160318"
+       orientation="0,-1"
+       id="guide293"
+       inkscape:locked="false" />
+    <inkscape:grid
+       type="xygrid"
+       id="grid295" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata5">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Capa 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(0,-284.30001)">
+    <g
+       id="g4133"
+       transform="matrix(0.9182611,0,0,0.9182611,-19.582232,24.4642)">
+      <path
+         style="fill:#232629;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.287462px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+         d="m 23.93778,289.55608 0.922529,-2.47895 2.563056,-0.57549 2.395943,-1.27803 2.491924,1.17578 0.401143,2.50266 1.620734,1.26491 0.381256,1.78538 -0.666013,1.62872 -1.326357,0.85434 -9.105041,0.0948 -1.530218,-1.13976 -0.31061,-1.71767 0.780558,-1.56852 z"
+         id="path1481"
+         sodipodi:nodetypes="ccccccccccccccc"
+         inkscape:export-filename="/home/ga/Proiektuak/opensnitch/gui/opensnitch/ui/lib/python3.9/site-packages/opensnitch/res/icon-white.png"
+         inkscape:export-xdpi="96"
+         inkscape:export-ydpi="96" />
+      <path
+         style="fill:#fbffff;fill-opacity:1;stroke:none;stroke-width:0.0267051;stroke-opacity:1"
+         d="m 23.924347,295.01517 c -1.190403,-0.17151 -2.160364,-1.04049 -2.48066,-2.22233 -0.06228,-0.22986 -0.06956,-0.30524 -0.06956,-0.7212 0,-0.41596 0.0072,-0.49134 0.06956,-0.72117 0.147237,-0.54329 0.408851,-0.99654 0.795944,-1.37896 0.324557,-0.32066 0.785409,-0.60618 1.154881,-0.71557 l 0.103224,-0.0303 0.01629,-0.29557 c 0.07143,-1.29674 0.972433,-2.37494 2.27078,-2.71736 0.229851,-0.0607 0.312438,-0.0688 0.716539,-0.0696 0.257818,-6.1e-4 0.509198,0.0123 0.577176,0.0292 0.120416,0.0303 0.120441,0.0303 0.198793,-0.0679 0.155946,-0.19524 0.509186,-0.51209 0.74373,-0.66706 0.551463,-0.36443 1.11147,-0.54697 1.774575,-0.57842 0.589067,-0.0277 1.121425,0.0806 1.650413,0.33595 0.72776,0.35149 1.243577,0.8611 1.599369,1.58011 0.255831,0.51699 0.367014,1.04428 0.341349,1.61884 l -0.01332,0.2989 0.217937,0.14177 c 0.119863,0.078 0.346565,0.26788 0.503782,0.42196 0.88975,0.87202 1.218873,2.08704 0.893766,3.29955 -0.132616,0.49457 -0.500547,1.11835 -0.881945,1.49513 -0.364404,0.36002 -1.001057,0.73363 -1.4664,0.86052 -0.492709,0.13439 -0.431399,0.13271 -4.634625,0.12938 -2.15745,-0.002 -3.994154,-0.0135 -4.081564,-0.0261 z m 8.365743,-0.89453 c 0.59725,-0.15778 1.128615,-0.51689 1.486943,-1.00491 0.147801,-0.20133 0.33126,-0.60206 0.39973,-0.87313 0.08448,-0.33442 0.08466,-0.85621 4.07e-4,-1.18961 -0.203279,-0.80452 -0.831632,-1.50173 -1.597272,-1.77233 -0.153083,-0.0541 -0.232971,-0.0955 -0.224805,-0.11652 0.252703,-0.65059 0.224901,-1.36673 -0.07754,-1.99749 -0.264858,-0.55243 -0.669292,-0.9522 -1.225766,-1.21167 -0.399047,-0.18607 -0.636305,-0.23736 -1.097982,-0.23736 -0.319298,0 -0.430775,0.0109 -0.61795,0.0601 -0.739241,0.19424 -1.358854,0.68643 -1.680928,1.33522 l -0.07478,0.15063 -0.122961,-0.0614 c -0.228775,-0.11421 -0.565847,-0.2025 -0.834911,-0.21871 -0.932118,-0.0562 -1.838882,0.55372 -2.13689,1.4371 -0.168757,0.50024 -0.151725,0.9779 0.05358,1.50243 0.01377,0.035 -0.0089,0.0394 -0.156728,0.03 -0.208357,-0.0132 -0.547143,0.0503 -0.81323,0.15241 -0.54704,0.20974 -1.043227,0.72489 -1.228933,1.27596 -0.172171,0.51087 -0.161028,0.97577 0.03539,1.47692 0.212384,0.54181 0.73321,1.03044 1.29576,1.21563 0.113249,0.0371 0.270424,0.0783 0.349276,0.0916 0.08114,0.0136 1.857609,0.0221 4.092874,0.0195 l 3.949506,-0.004 z m -5.761885,-1.40118 c -0.576304,-0.34169 -1.047827,-0.63319 -1.047827,-0.64782 0,-0.0148 0.471523,-0.30613 1.047827,-0.64783 l 1.047829,-0.62116 0.0074,0.42212 0.0074,0.42218 h 1.718841 1.718847 v 0.42469 0.42469 h -1.718876 -1.718849 l -0.0074,0.42218 -0.0074,0.42217 z m 2.350898,-2.34355 v -0.42777 h -1.719512 -1.719514 v -0.42468 -0.42471 h 1.718847 1.718849 l 0.0074,-0.42217 0.0074,-0.42216 1.047812,0.6212 c 0.5763,0.34168 1.051205,0.63124 1.055344,0.64353 0.0041,0.0124 -0.443196,0.28902 -0.99408,0.61507 -0.550883,0.32599 -1.028812,0.61 -1.062059,0.63111 l -0.06046,0.0382 z"
+         id="path826-6"
+         inkscape:connector-curvature="0" />
+    </g>
+  </g>
+</svg>
diff --git a/ui/resources/io.github.evilsocket.opensnitch.appdata.xml b/ui/resources/io.github.evilsocket.opensnitch.appdata.xml
new file mode 100644 (file)
index 0000000..b55aff9
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop-application">
+  <id>io.github.evilsocket.opensnitch</id>
+  
+  <name>OpenSnitch</name>
+  <summary>GNU/Linux interactive application firewall</summary>
+  
+  <metadata_license>FTL</metadata_license>
+  <project_license>GPL-3.0-or-later</project_license>
+  
+  <supports>
+    <control>pointing</control>
+    <control>keyboard</control>
+    <control>touch</control>
+  </supports>
+  
+  <description>
+    <p>
+      Whenever a program tries to establish a new connection, it&apos;ll prompt the user to allow or deny it.
+    </p>
+    <p>
+      The user can decide if block the outgoing connection based on properties of the connection: by port, by uid, by dst ip, by program or a combination of them. These rules can last forever, until the app restart or just one time.
+    </p>
+    <p>
+      The GUI allows the user to view live outgoing connections, as well as search by process, user, host or port.
+    </p>
+    <p>
+      OpenSnitch can also work as a system-wide domains blocker, by using lists of domains, list of IPs or list of regular expressions.
+    </p>
+  </description>
+
+  <categories>
+    <category>System</category>
+    <category>Security</category>
+    <category>Monitor</category>
+    <category>Network</category>
+  </categories>
+
+  <icon type="stock">opensnitch-ui</icon>
+  <url type="homepage">https://github.com/evilsocket/opensnitch</url>
+  <url type="bugtracker">https://github.com/evilsocket/opensnitch/issues</url>
+  <url type="help">https://github.com/evilsocket/opensnitch/wiki</url>
+  <launchable type="desktop-id">opensnitch_ui.desktop</launchable>
+  <screenshots>
+    <screenshot type="default">
+      <image>https://user-images.githubusercontent.com/2742953/85205382-6ba9cb00-b31b-11ea-8e9a-bd4b8b05a236.png</image>
+    </screenshot>
+    <screenshot>
+      <image>https://user-images.githubusercontent.com/2742953/217039798-3477c6c2-d64f-4eea-89af-cd94ee77cff4.png</image>
+    </screenshot>
+    <screenshot>
+      <image>https://user-images.githubusercontent.com/2742953/99863173-3987e800-2b9d-11eb-93f2-fe3121b18c51.png</image>
+    </screenshot>
+  </screenshots>
+  <content_rating type="oars-1.0" />
+</component>
diff --git a/ui/resources/kcm_opensnitch.desktop b/ui/resources/kcm_opensnitch.desktop
new file mode 100644 (file)
index 0000000..ee2c112
--- /dev/null
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Exec=opensnitch-ui
+Icon=opensnitch-ui
+Type=Service
+X-KDE-ServiceTypes=SystemSettingsExternalApp
+Name=OpenSnitch Firewall
+Comment=OpenSnitch Firewall Graphical Interface
+X-KDE-Keywords=system,firewall,policies,security,polkit,policykit,douane
+X-KDE-Autostart-after=panel
diff --git a/ui/resources/opensnitch_ui.desktop b/ui/resources/opensnitch_ui.desktop
new file mode 100644 (file)
index 0000000..782309f
--- /dev/null
@@ -0,0 +1,18 @@
+[Desktop Entry]
+Type=Application
+Name=OpenSnitch
+Exec=opensnitch-ui
+Icon=opensnitch-ui
+GenericName=OpenSnitch Firewall
+GenericName[hu]=OpenSnitch-tűzfal
+GenericName[nb]=OpenSnitch brannmur
+Comment=Interactive application firewall
+Comment[es]=Firewall de aplicaciones
+Comment[hu]=Alkalmazási tűzfal
+Comment[nb]=Interaktiv programbrannmur
+Terminal=false
+NoDisplay=false
+Categories=System;Security;Monitor;Network;
+Keywords=system;firewall;policies;security;polkit;policykit;
+X-GNOME-Autostart-Delay=3
+X-GNOME-Autostart-enabled=true
diff --git a/ui/setup.py b/ui/setup.py
new file mode 100644 (file)
index 0000000..ee11d73
--- /dev/null
@@ -0,0 +1,38 @@
+from setuptools import setup, find_packages
+
+import os
+import sys
+
+path = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(path)
+
+from opensnitch.version import version
+
+setup(name='opensnitch-ui',
+      version=version,
+      description='Prompt service and UI for the opensnitch interactive firewall application.',
+      long_description='GUI for the opensnitch interactive firewall application\n\
+opensnitch-ui is a GUI for opensnitch written in Python.\n\
+It allows the user to view live outgoing connections, as well as search\n\
+to make connections.\n\
+.\n\
+The user can decide if block the outgoing connection based on properties of\n\
+the connection: by port, by uid, by dst ip, by program or a combination\n\
+of them.\n\
+.\n\
+These rules can last forever, until the app restart or just one time.',
+      url='https://github.com/evilsocket/opensnitch',
+      author='Simone "evilsocket" Margaritelli',
+      author_email='evilsocket@protonmail.com',
+      license='GPL-3.0',
+      packages=find_packages(),
+      include_package_data = True,
+      package_data={'': ['*.*']},
+      data_files=[('/usr/share/applications', ['resources/opensnitch_ui.desktop']),
+               ('/usr/share/kservices5', ['resources/kcm_opensnitch.desktop']),
+               ('/usr/share/icons/hicolor/scalable/apps', ['resources/icons/opensnitch-ui.svg']),
+               ('/usr/share/icons/hicolor/48x48/apps', ['resources/icons/48x48/opensnitch-ui.png']),
+               ('/usr/share/icons/hicolor/64x64/apps', ['resources/icons/64x64/opensnitch-ui.png']),
+               ('/usr/share/metainfo', ['resources/io.github.evilsocket.opensnitch.appdata.xml'])],
+      scripts = [ 'bin/opensnitch-ui' ],
+      zip_safe=False)
diff --git a/ui/tests/README.md b/ui/tests/README.md
new file mode 100644 (file)
index 0000000..e94453b
--- /dev/null
@@ -0,0 +1,23 @@
+GUI unit tests.
+
+We use pytest [0] to pytest-qt [1] to test GUI code.
+
+To run the tests: `cd tests; pytest -v`
+
+TODO:
+ - test service class (Service.py)
+ - test events window (stats.py):
+   - The size of the window must be saved on close, and restored when opening it again.
+   - Columns width of every view must be saved and restored properly.
+   - On the Events tab, clicking on the Node, Process or Rule column must jump to the detailed view of the selected item.
+   - When entering into a detail view:
+     - the results limit configured must be respected (that little button on the bottom right of every tab).
+     - must apply the proper SQL query for every detailed view.
+   - When going back from a detail view:
+     - The SQL query must be restored.
+   - Test rules context menu actions.
+   - Test select rows and copy them to the clipboard (ctrl+c).
+
+
+0. https://docs.pytest.org/en/6.2.x/
+1. https://pytest-qt.readthedocs.io/en/latest/intro.html
diff --git a/ui/tests/__init__.py b/ui/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/ui/tests/dialogs/__init__.py b/ui/tests/dialogs/__init__.py
new file mode 100644 (file)
index 0000000..c562095
--- /dev/null
@@ -0,0 +1,54 @@
+
+from opensnitch.database import Database
+from opensnitch.config import Config
+from opensnitch.nodes import Nodes
+
+# grpc object
+class ClientConfig:
+    version = "1.2.3"
+    name = "bla"
+    logLevel = 0
+    isFirewallRunning = False
+    rules = []
+    config = '''{
+    "Server":{
+        "Address": "unix:///tmp/osui.sock",
+        "LogFile": "/var/log/opensnitchd.log"
+    },
+    "DefaultAction": "deny",
+    "DefaultDuration": "once",
+    "InterceptUnknown": false,
+    "ProcMonitorMethod": "ebpf",
+    "LogLevel": 0,
+    "LogUTC": true,
+    "LogMicro": false,
+    "Firewall": "iptables",
+    "Stats": {
+        "MaxEvents": 150,
+        "MaxStats": 50
+    }
+    }
+    '''
+
+class Connection:
+    protocol = "tcp"
+    src_ip = "127.0.0.1"
+    src_port = "12345"
+    dst_ip = "127.0.0.1"
+    dst_host = "localhost"
+    dst_port = "54321"
+    user_id = 1000
+    process_id = 9876
+    process_path = "/bin/cmd"
+    process_cwd = "/tmp"
+    process_args = "/bin/cmd --parm1 test"
+    process_env = []
+
+db = Database.instance()
+db.initialize()
+Config.init()
+
+nodes = Nodes.instance()
+nodes._nodes["unix:/tmp/osui.sock"] = {
+    'data': ClientConfig
+}
diff --git a/ui/tests/dialogs/test_preferences.py b/ui/tests/dialogs/test_preferences.py
new file mode 100644 (file)
index 0000000..62607b1
--- /dev/null
@@ -0,0 +1,146 @@
+#
+# pytest -v tests/dialogs/test_ruleseditor.py
+#
+import os
+import time
+import json
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from opensnitch.config import Config
+from opensnitch.dialogs.preferences import PreferencesDialog
+
+class TestPreferences():
+
+    @classmethod
+    def reset_settings(self):
+        try:
+            os.remove(os.environ['HOME'] + "/.config/opensnitch/settings.conf")
+        except Exception:
+            pass
+
+    @classmethod
+    def setup_method(self):
+        white_icon = QtGui.QIcon("../res/icon-white.svg")
+        self.reset_settings()
+        self.prefs = PreferencesDialog(appicon=white_icon)
+        self.prefs.show()
+
+    def run(self, qtbot):
+        def handle_dialog():
+            qtbot.mouseClick(self.prefs.applyButton, QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.prefs.acceptButton, QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(500, handle_dialog)
+        self.prefs.exec_()
+
+    def test_save_popups_settings(self, qtbot):
+        """ Test saving UI related settings.
+        """
+        qtbot.addWidget(self.prefs)
+
+        self.prefs.comboUIAction.setCurrentIndex(Config.ACTION_ALLOW_IDX)
+        self.prefs.comboUITarget.setCurrentIndex(2)
+        self.prefs.comboUIDuration.setCurrentIndex(4)
+        self.prefs.comboUIDialogPos.setCurrentIndex(2)
+        self.prefs.spinUITimeout.setValue(30)
+        self.prefs.showAdvancedCheck.setChecked(True)
+        self.prefs.uidCheck.setChecked(True)
+
+        self.run(qtbot)
+
+        assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_ACTION_KEY) == Config.ACTION_ALLOW_IDX and self.prefs.comboUIAction.currentText() == Config.ACTION_ALLOW
+        assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_TARGET_KEY) == 2
+        assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_DURATION_KEY) == 4
+        assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_TIMEOUT_KEY) == 30
+        assert self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_POPUP_POSITION) == 2
+        assert self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_POPUP_ADVANCED) == True
+        assert self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_POPUP_ADVANCED_UID) == True
+
+    def test_save_ui_settings(self, qtbot):
+        self.prefs.checkUIRules.setChecked(True)
+        self.prefs.comboUIRules.setCurrentIndex(1)
+        self.prefs.checkHideNode.setChecked(False)
+        self.prefs.checkHideProto.setChecked(False)
+
+        self.run(qtbot)
+
+        assert self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_IGNORE_RULES) == True and  self.prefs._cfg.getInt(self.prefs._cfg.DEFAULT_IGNORE_TEMPORARY_RULES) == 1
+        cols = self.prefs._cfg.getSettings(Config.STATS_SHOW_COLUMNS)
+        assert cols == ['0','2','3','5','6']
+
+    def test_save_node_settings(self, qtbot, capsys):
+        self.prefs.comboNodeAction.setCurrentIndex(Config.ACTION_ALLOW_IDX)
+        self.prefs.comboNodeMonitorMethod.setCurrentIndex(2)
+        self.prefs.comboNodeLogLevel.setCurrentIndex(5)
+        self.prefs.checkNodeLogUTC.setChecked(False)
+        self.prefs.checkNodeLogMicro.setChecked(True)
+        self.prefs.checkInterceptUnknown.setChecked(True)
+        self.prefs.tabWidget.setCurrentIndex(self.prefs.TAB_NODES)
+        self.prefs._node_needs_update = True
+
+        self.run(qtbot)
+
+        assert len(self.prefs._notifications_sent) == 1
+        for n in self.prefs._notifications_sent:
+            conf = json.loads(self.prefs._notifications_sent[n].data)
+            assert conf['InterceptUnknown'] == True
+            assert conf['ProcMonitorMethod'] == "audit"
+            assert conf['LogLevel'] == 5
+            assert conf['LogUTC'] == False
+            assert conf['LogMicro'] == True
+            assert conf['DefaultAction'] == "allow"
+
+# TODO: click on the QMessageDialog
+#
+#    def test_save_db_settings(self, qtbot, monkeypatch, capsys):
+#        self.prefs.comboDBType.setCurrentIndex(1)
+#        self.prefs.dbLabel.setText('/tmp/test.db')
+#
+#        def handle_dialog():
+#            qtbot.mouseClick(self.prefs.applyButton, QtCore.Qt.LeftButton)
+#            # after saving the settings, a warning dialog must appear, informing
+#            # the user to restart the GUI
+#            time.sleep(.5)
+#            msgbox = QtWidgets.QApplication.activeModalWidget()
+#            try:
+#                assert msgbox != None
+#                okBtn = msgbox.button(QtWidgets.QMessageBox.Ok)
+#                qtbot.mouseClick(okBtn, QtCore.Qt.LeftButton)
+#            except Exception as e:
+#                print("test_save_db_Settings() exception:", e)
+#            qtbot.mouseClick(self.prefs.acceptButton, QtCore.Qt.LeftButton)
+#
+#        QtCore.QTimer.singleShot(500, handle_dialog)
+#        self.prefs.exec_()
+
+#        assert self.prefs._cfg.getInt(Config.DEFAULT_DB_TYPE_KEY) == 1
+#        assert self.prefs._cfg.getSettings(Config.DEFAULT_DB_FILE_KEY) == '/tmp/test.db'
+
+    def test_load_ui_settings(self, qtbot, capsys):
+        """ reTest saved settings (load_settings()).
+        On dialog show up the widgets must be configured properly, with the settings
+        configured in previous tests.
+        """
+        self.prefs.checkUIRules.setChecked(False)
+        self.prefs.comboUIRules.setCurrentIndex(0)
+        self.prefs.comboUITarget.setCurrentIndex(0)
+        self.prefs.comboUIDuration.setCurrentIndex(0)
+        self.prefs.checkHideNode.setChecked(True)
+        self.prefs.checkHideProto.setChecked(True)
+
+        def handle_dialog():
+            qtbot.mouseClick(self.prefs.cancelButton, QtCore.Qt.LeftButton)
+        QtCore.QTimer.singleShot(500, handle_dialog)
+
+        self.prefs.exec_()
+        self.prefs.show()
+
+        print(self.prefs._cfg.getBool(self.prefs._cfg.DEFAULT_IGNORE_RULES))
+
+        assert self.prefs.comboUIAction.currentIndex() == Config.ACTION_ALLOW_IDX and self.prefs.comboUIAction.currentText() == Config.ACTION_ALLOW
+        assert self.prefs.checkUIRules.isChecked() == True
+        assert self.prefs.comboUIRules.currentIndex() == 1
+        assert self.prefs.comboUITarget.currentIndex() == 2
+        assert self.prefs.comboUIDuration.currentIndex() == 4 and self.prefs.comboUIDuration.currentText() == Config.DURATION_30m
+        assert self.prefs.comboUIDialogPos.currentIndex() == 2
+        assert self.prefs.spinUITimeout.value() == 30
diff --git a/ui/tests/dialogs/test_ruleseditor.py b/ui/tests/dialogs/test_ruleseditor.py
new file mode 100644 (file)
index 0000000..2b06bd1
--- /dev/null
@@ -0,0 +1,384 @@
+#
+# pytest -v tests/dialogs/test_ruleseditor.py
+#
+
+import json
+from PyQt5 import QtCore, QtWidgets, QtGui
+
+from opensnitch.config import Config
+from opensnitch.dialogs.ruleseditor import RulesEditorDialog
+
+class TestRulesEditor():
+
+    @classmethod
+    def setup_method(self):
+        white_icon = QtGui.QIcon("../res/icon-white.svg")
+        self.rd = RulesEditorDialog(appicon=white_icon)
+        self.rd.show()
+        self.rd.ruleNameEdit.setText("xxx")
+        self.rd.nodesCombo.addItem("unix:/tmp/osui.sock")
+        self.rd.nodesCombo.setCurrentText("unix:/tmp/osui.sock")
+        self.rd._nodes._nodes["unix:/tmp/osui.sock"] = {}
+
+    def test_rule_no_fields(self, qtbot):
+        """ Test that rules without fields selected cannot be created.
+        """
+        qtbot.addWidget(self.rd)
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+        assert self.rd.statusLabel.text() != ""
+
+    def test_fields_empty(self, qtbot):
+        """ Test that fields cannot be empty.
+        """
+
+        self.rd.pidCheck.setChecked(True)
+        self.rd.pidLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+        self.rd.pidCheck.setChecked(False)
+        self.rd.uidCheck.setChecked(True)
+        self.rd.uidLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+        self.rd.uidCheck.setChecked(False)
+        self.rd.procCheck.setChecked(True)
+        self.rd.procLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+        self.rd.procCheck.setChecked(False)
+        self.rd.cmdlineCheck.setChecked(True)
+        self.rd.cmdlineLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+        self.rd.cmdlineCheck.setChecked(False)
+        self.rd.dstPortCheck.setChecked(True)
+        self.rd.dstPortLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+        self.rd.dstPortCheck.setChecked(False)
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+        self.rd.dstHostCheck.setChecked(False)
+        self.rd.dstListsCheck.setChecked(True)
+        self.rd.dstListsLine.setText("")
+        result, error = self.rd._save_rule()
+        assert error != None
+
+    def test_add_basic_rule(self, qtbot):
+        """ Test adding a basic rule.
+        """
+
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test.com")
+        self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_UNTIL_RESTART))
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
+        assert self.rd.rule.operator.operand == "dest.host"
+        assert self.rd.rule.operator.data == "www.test.com"
+        assert self.rd.rule.duration == Config.DURATION_UNTIL_RESTART
+
+    def test_add_complex_rule(self, qtbot):
+        """ Test add complex rule.
+        """
+        self.rd.WORK_MODE = self.rd.ADD_RULE
+        self.rd._reset_state()
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-complex.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test-complex.com")
+        self.rd.dstPortCheck.setChecked(True)
+        self.rd.dstPortLine.setText("443")
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-complex.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-complex.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_LIST
+        assert self.rd.rule.operator.operand == Config.RULE_TYPE_LIST
+        json_rule = json.loads(self.rd.rule.operator.data)
+        assert json_rule[0]['type'] == "simple"
+        assert json_rule[0]['operand'] == "dest.port"
+        assert json_rule[0]['data'] == "443"
+        assert json_rule[1]['type'] == "simple"
+        assert json_rule[1]['operand'] == "dest.host"
+        assert json_rule[1]['data'] == "www.test-complex.com"
+
+    def test_add_reject_rule(self, qtbot):
+        """ Test adding new rule with action "reject".
+        """
+
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-reject.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test-reject.com")
+        self.rd.actionRejectRadio.setChecked(True)
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-reject.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-reject.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
+        assert self.rd.rule.operator.operand == "dest.host"
+        assert self.rd.rule.operator.data == "www.test-reject.com"
+        assert self.rd.rule.action == Config.ACTION_REJECT
+
+    def test_add_deny_rule(self, qtbot):
+        """ Test adding new rule with action "deny".
+        """
+
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-deny.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test-deny.com")
+        self.rd.actionDenyRadio.setChecked(True)
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-deny.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-deny.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
+        assert self.rd.rule.operator.operand == "dest.host"
+        assert self.rd.rule.operator.data == "www.test-deny.com"
+        assert self.rd.rule.action == Config.ACTION_DENY
+
+    def test_add_allow_rule(self, qtbot):
+        """ Test adding new rule with action "allow".
+        """
+
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-allow.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test-allow.com")
+        self.rd.actionAllowRadio.setChecked(True)
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-allow.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-allow.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
+        assert self.rd.rule.operator.operand == "dest.host"
+        assert self.rd.rule.operator.data == "www.test-allow.com"
+        assert self.rd.rule.action == Config.ACTION_ALLOW
+
+    def test_add_rule_name_conflict(self, qtbot):
+        """ Test that rules with the same name cannot be added.
+        """
+        assert self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText()).next() == True
+
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test.com")
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() != ""
+
+    def test_load_rule(self, qtbot):
+        """ Test loading a rule.
+        """
+        self.rd.WORK_MODE = self.rd.ADD_RULE
+        self.rd._reset_state()
+        records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText())
+        assert records.next() == True
+
+        self.rd.edit_rule(records, self.rd.nodesCombo.currentText())
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.ruleNameEdit.text() == "www.test.com"
+        assert self.rd.dstHostCheck.isChecked() == True
+        assert self.rd.dstHostLine.text() == "www.test.com"
+        assert self.rd.durationCombo.currentIndex() == self.rd._load_duration(Config.DURATION_UNTIL_RESTART)
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+    def test_edit_and_rename_rule(self, qtbot):
+        """ Test loading, editing and renaming a rule.
+        """
+        self.rd.WORK_MODE = self.rd.ADD_RULE
+        self.rd._reset_state()
+        records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText())
+        assert records.next() == True
+
+        self.rd.edit_rule(records, self.rd.nodesCombo.currentText())
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.ruleNameEdit.text() == "www.test.com"
+        assert self.rd.dstHostCheck.isChecked() == True
+        assert self.rd.dstHostLine.text() == "www.test.com"
+
+        self.rd.ruleNameEdit.setText("www.test-renamed.com")
+        self.rd.dstHostLine.setText("www.test-renamed.com")
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        records = self.rd._db.get_rule("www.test.com", self.rd.nodesCombo.currentText())
+        assert records.next() == False
+        records = self.rd._db.get_rule("www.test-renamed.com", self.rd.nodesCombo.currentText())
+        assert records.next() == True
+
+    def test_durations(self, qtbot):
+        """ Test adding new rule with action "deny".
+        """
+
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-duration.com")
+        self.rd.dstHostCheck.setChecked(True)
+        self.rd.dstHostLine.setText("www.test-duration.com")
+        self.rd.actionDenyRadio.setChecked(True)
+        self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_ALWAYS))
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-duration.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-duration.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_SIMPLE
+        assert self.rd.rule.operator.operand == "dest.host"
+        assert self.rd.rule.operator.data == "www.test-duration.com"
+        assert self.rd.rule.action == Config.ACTION_DENY
+        assert self.rd.rule.duration == Config.DURATION_ALWAYS
+
+    def test_rule_LANs(self, qtbot):
+        """ Test rule with regexp and LAN keyword in particular.
+        """
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-rule-LAN.com")
+        self.rd.dstIPCheck.setChecked(True)
+        self.rd.dstIPCombo.setCurrentText(self.rd.LAN_LABEL)
+        self.rd.actionDenyRadio.setChecked(True)
+        self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_ALWAYS))
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-rule-LAN.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-rule-LAN.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_REGEXP
+        assert self.rd.rule.operator.operand == "dest.ip"
+        assert self.rd.rule.operator.data == self.rd.LAN_RANGES
+        assert self.rd.rule.action == Config.ACTION_DENY
+        assert self.rd.rule.duration == Config.DURATION_ALWAYS
+
+    def test_rule_networks(self, qtbot):
+        """ Test rule with networks.
+        """
+        self.rd.statusLabel.setText("")
+        self.rd.ruleNameEdit.setText("www.test-rule-networks.com")
+        self.rd.dstIPCheck.setChecked(True)
+        self.rd.dstIPCombo.setCurrentText("192.168.111.0/24")
+        self.rd.actionDenyRadio.setChecked(True)
+        self.rd.durationCombo.setCurrentIndex(self.rd._load_duration(Config.DURATION_ALWAYS))
+
+        def handle_dialog():
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Apply), QtCore.Qt.LeftButton)
+            qtbot.mouseClick(self.rd.buttonBox.button(QtWidgets.QDialogButtonBox.Close), QtCore.Qt.LeftButton)
+
+        QtCore.QTimer.singleShot(100, handle_dialog)
+        self.rd.exec_()
+
+        assert self.rd.statusLabel.text() == ""
+        assert self.rd._db.get_rule("www.test-rule-networks.com", self.rd.nodesCombo.currentText()).next() == True
+        assert self.rd._old_rule_name == "www.test-rule-networks.com"
+        # after adding a rule, we enter into editing mode, to allow editing it
+        # without closing the dialog.
+        assert self.rd.WORK_MODE == self.rd.EDIT_RULE
+        assert self.rd.rule.operator.type == Config.RULE_TYPE_NETWORK
+        assert self.rd.rule.operator.operand == "dest.network"
+        assert self.rd.rule.operator.data == "192.168.111.0/24"
+        assert self.rd.rule.action == Config.ACTION_DENY
+        assert self.rd.rule.duration == Config.DURATION_ALWAYS
+
diff --git a/ui/tests/test_nodes.py b/ui/tests/test_nodes.py
new file mode 100644 (file)
index 0000000..d4c4ae5
--- /dev/null
@@ -0,0 +1,149 @@
+#
+# pytest -v tests/nodes.py
+#
+
+import json
+from PyQt5 import QtCore
+from opensnitch import ui_pb2
+from opensnitch.config import Config
+from opensnitch.nodes import Nodes
+from tests.dialogs import ClientConfig
+
+class NotifTest(QtCore.QObject):
+    """We need to subclass from QObject in order to be able to user signals and slots.
+    """
+    signal = QtCore.pyqtSignal(ui_pb2.NotificationReply)
+
+    @QtCore.pyqtSlot(ui_pb2.NotificationReply)
+    def callback(self, reply):
+        assert reply != None
+        assert reply.code == ui_pb2.OK and reply.type == ui_pb2.LOAD_FIREWALL and reply.data == "test"
+
+
+class TestNodes():
+
+    @classmethod
+    def setup_method(self):
+        self.nid = None
+        self.daemon_config = ClientConfig
+        self.nodes = Nodes.instance()
+        self.nodes._db.insert("nodes",
+                              "(addr, status, hostname, daemon_version, daemon_uptime, " \
+                              "daemon_rules, cons, cons_dropped, version, last_connection)",
+                              (
+                                  "1.2.3.4", Nodes.ONLINE, "xxx", "v1.2.3", str(0),
+                                  "", "0", "0", "",
+                                  "2022-01-03 11:22:48.101624"
+                              )
+                              )
+
+
+    def test_add(self, qtbot):
+        node = self.nodes.add("peer:1.2.3.4", self.daemon_config)
+
+        assert node != None
+
+    def test_get_node(self, qtbot):
+        node = self.nodes.get_node("peer:1.2.3.4")
+
+        assert node != None
+
+    def test_get_addr(self, qtbot):
+        proto, addr = self.nodes.get_addr("peer:1.2.3.4")
+
+        assert proto == "peer" and addr == "1.2.3.4"
+
+    def test_get_nodes(self, qtbot):
+        nodes = self.nodes.get_nodes()
+        print(nodes)
+
+        assert nodes.get("peer:1.2.3.4") != None
+
+    def test_add_rule(self, qtbot):
+        self.nodes.add_rule(
+            "2022-01-03 11:22:48.101624",
+            "peer:1.2.3.4",
+            "test",
+            True,
+            False,
+            Config.ACTION_ALLOW, Config.DURATION_30s,
+            Config.RULE_TYPE_SIMPLE, False, "dest.host", ""
+        )
+
+        query = self.nodes._db.get_rule("test", "peer:1.2.3.4")
+
+        assert query.first() == True
+        assert query.record().value(0) == "2022-01-03 11:22:48.101624"
+        assert query.record().value(1) == "peer:1.2.3.4"
+        assert query.record().value(2) == "test"
+        assert query.record().value(3) == "1"
+        assert query.record().value(4) == "0"
+        assert query.record().value(5) == Config.ACTION_ALLOW
+        assert query.record().value(6) == Config.DURATION_30s
+        assert query.record().value(7) == Config.RULE_TYPE_SIMPLE
+        assert query.record().value(8) == "0"
+        assert query.record().value(9) == "dest.host"
+
+    def test_update_rule_time(self, qtbot):
+        query = self.nodes._db.get_rule("test", "peer:1.2.3.4")
+        assert query.first() == True
+        assert query.record().value(0) == "2022-01-03 11:22:48.101624"
+
+        self.nodes.update_rule_time("2022-01-03 21:22:48.101624", "test", "peer:1.2.3.4")
+        query = self.nodes._db.get_rule("test", "peer:1.2.3.4")
+        assert query.first() == True
+        assert query.record().value(0) == "2022-01-03 21:22:48.101624"
+
+    def test_delete_rule(self, qtbot):
+        query = self.nodes._db.get_rule("test", "peer:1.2.3.4")
+        assert query.first() == True
+
+        self.nodes.delete_rule("test", "peer:1.2.3.4", None)
+        query = self.nodes._db.get_rule("test", "peer:1.2.3.4")
+        assert query.first() == False
+
+    def test_update_node_status(self, qtbot):
+        query = self.nodes._db.select("SELECT status FROM nodes WHERE addr = '{0}'".format("1.2.3.4"))
+        assert query != None and query.exec_() == True and query.first() == True
+        assert query.record().value(0) == Nodes.ONLINE
+
+        self.nodes.update("peer", "1.2.3.4", Nodes.OFFLINE)
+        query = self.nodes._db.select("SELECT status FROM nodes WHERE addr = '{0}'".format("1.2.3.4"))
+        assert query != None and query.exec_() == True and query.first() == True
+        assert query.record().value(0) == Nodes.OFFLINE
+
+    def test_send_notification(self, qtbot):
+        notifs = NotifTest()
+        notifs.signal.connect(notifs.callback)
+
+        test_notif = ui_pb2.Notification(
+            clientName="",
+            serverName="",
+            type=ui_pb2.LOAD_FIREWALL,
+            data="test",
+            rules=[])
+
+        self.nid = self.nodes.send_notification("peer:1.2.3.4", test_notif, notifs.signal)
+        assert self.nodes._notifications_sent[self.nid] != None
+        assert self.nodes._notifications_sent[self.nid]['type'] == ui_pb2.LOAD_FIREWALL
+
+    def test_reply_notification(self, qtbot):
+        reply_notif = ui_pb2.Notification(
+            id = self.nid,
+            clientName="",
+            serverName="",
+            type=ui_pb2.LOAD_FIREWALL,
+            data="test",
+            rules=[])
+        # just after process the reply, the notification is deleted (except if
+        # is of type MONITOR_PROCESS
+        self.nodes.reply_notification("peer:1.2.3.4", reply_notif)
+        assert self.nid not in self.nodes._notifications_sent
+
+    def test_delete(self, qtbot):
+        self.nodes.delete("peer:1.2.3.4")
+        node = self.nodes.get_node("peer:1.2.3.4")
+        nodes = self.nodes.get_nodes()
+
+        assert node == None
+        assert nodes.get("peer:1.2.3.4") == None
diff --git a/utils/legacy/make_ads_rules.py b/utils/legacy/make_ads_rules.py
new file mode 100644 (file)
index 0000000..16341c5
--- /dev/null
@@ -0,0 +1,69 @@
+import requests
+import re
+import ipaddress
+import datetime
+import os
+
+lists = ( \
+    "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts",
+    "https://mirror1.malwaredomains.com/files/justdomains",
+    "https://sysctl.org/cameleon/hosts",
+    "https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist",
+    "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt",
+    "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt",
+    "https://hosts-file.net/ad_servers.txt" )
+
+domains = {}
+
+for url in lists:
+    print "Downloading %s ..." % url
+    r = requests.get(url)
+    if r.status_code != 200:
+        print "Error, status code %d" % r.status_code
+        continue
+
+    for line in r.text.split("\n"):
+        line = line.strip()
+        if line == "":
+            continue
+
+        elif line[0] == "#":
+            continue
+
+        for part in re.split(r'\s+', line):
+            part = part.strip()
+            if part == "":
+                continue
+
+            try:
+                duh = ipaddress.ip_address(part)
+            except ValueError:
+                if part != "localhost":
+                    domains[part] = 1
+
+print "Got %d unique domains, saving as rules to ./rules/ ..." % len(domains)
+
+os.system("mkdir -p rules")
+
+idx = 0
+for domain, _ in domains.iteritems():
+    with open("rules/adv-%d.json" % idx, "wt") as fp:
+        tpl = """
+{
+   "created": "%s",
+   "updated": "%s",
+   "name": "deny-adv-%d",
+   "enabled": true,
+   "action": "deny",
+   "duration": "always",
+   "operator": {
+     "type": "simple",
+     "operand": "dest.host",
+     "data": "%s"
+   }
+}"""
+        now = datetime.datetime.utcnow().isoformat("T") + "Z"
+        data = tpl % ( now, now, idx, domain )
+        fp.write(data)
+
+    idx = idx + 1
diff --git a/utils/packaging/build_modules.sh b/utils/packaging/build_modules.sh
new file mode 100644 (file)
index 0000000..51abc7b
--- /dev/null
@@ -0,0 +1,76 @@
+#!/bin/sh
+#
+# opensnitch - 2022-2023
+#
+echo """
+
+  Dependencies needed to compile the eBPF modules:
+  sudo apt install -y wget flex bison ca-certificates wget python3 rsync bc libssl-dev clang llvm libelf-dev libzip-dev git libpcap-dev
+  ---
+"""
+
+kernel_version=$(uname -r | cut -d. -f1,2)
+if [ ! -z $1 ]; then
+    kernel_version=$1
+fi
+
+kernel_sources="v${kernel_version}.tar.gz"
+
+if [ -f "${kernel_sources}" ]; then
+    echo -n "[i] Deleting previous kernel sources ${kernel_sources}: "
+    rm -f ${kernel_sources} && echo "OK" || echo "ERROR"
+fi
+echo "[+] Downloading kernel sources:"
+wget -nv --show-progress https://github.com/torvalds/linux/archive/${kernel_sources} 1>/dev/null
+echo
+
+if [ -d "linux-${kernel_version}/" ]; then
+    echo -n "[i] Deleting previous kernel sources dir linux-${kernel_version}/: "
+    rm -rf linux-${kernel_version}/ && echo "OK" || echo "ERROR"
+fi
+echo -n "[+] Uncompressing kernel sources: "
+tar -xf v${kernel_version}.tar.gz && echo "OK" || echo "ERROR"
+
+if [ "${ARCH}" == "arm" -o "${ARCH}" == "arm64" ]; then
+    echo "[+] Patching kernel sources"
+    patch linux-${kernel_version}/arch/arm/include/asm/unified.h < ebpf_prog/arm-clang-asm-fix.patch
+fi
+
+echo -n "[+] Preparing kernel sources... (1-2 minutes): "
+echo -n "."
+cd linux-${kernel_version} && yes "" | make oldconfig 1>/dev/null
+echo -n "."
+make prepare 1>/dev/null
+echo -n "."
+make headers_install 1>/dev/null
+echo " DONE"
+cd ../
+
+if [ -z $ARCH ]; then
+    ARCH=x86
+fi
+
+echo "[+] Compiling eBPF modules..."
+cd ebpf_prog && make KERNEL_DIR=../linux-${kernel_version} KERNEL_HEADERS=../linux-${kernel_version} ARCH=${ARCH} >/dev/null
+# objdump -h opensnitch.o #you should see many section, number 1 should be called kprobe/tcp_v4_connect
+
+if [ ! -d modules/ ]; then
+    mkdir modules/
+fi
+mv opensnitch*o modules/
+cd ../
+llvm-strip -g ebpf_prog/modules/opensnitch*.o #remove debug info
+
+if [ -f ebpf_prog/modules/opensnitch.o ]; then
+    echo
+    if objdump -h ebpf_prog/modules/opensnitch.o | grep "kprobe/tcp_v4_connect"; then
+        ls ebpf_prog/modules/*.o
+        echo -e "\n * eBPF modules compiled. Now you can copy the *.o files to /etc/opensnitchd/ and restart the daemon\n"
+    else
+        echo -e "\n [WARN] opensnitch.o module not valid\n"
+        exit 1
+    fi
+else
+    echo -e "\n [WARN] opensnitch.o module not compiled\n"
+    exit 1
+fi
diff --git a/utils/packaging/daemon/deb/debian/NEWS b/utils/packaging/daemon/deb/debian/NEWS
new file mode 100644 (file)
index 0000000..837c995
--- /dev/null
@@ -0,0 +1,20 @@
+opensnitch (1.6.0-rc.3-1) unstable; urgency=medium
+
+    From now on the eBPF modules will be installed under
+    /usr/lib/opensnitchd/ebpf/.
+
+    The daemon will look for the eBPF modules in these directories and order:
+    - /usr/local/lib/opensnitchd/ebpf/
+    - /usr/lib/opensnitchd/ebpf/
+
+    Modules under /etc/opensnitchd/ will still be loaded if found, but it's
+    deprecated and will be removed in the future.
+
+    There's a new module to intercept processes execution. It may cause some
+    rules not to match: for example if you allowed /bin/telnet, now it may be
+    reported as /usr/bin/inteutils-telnet
+    
+    These cases are mostly expected. We'll keep improving it, sorry for
+    the inconveniences.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Wed, 19 Oct 2022 00:15:19 +0200
diff --git a/utils/packaging/daemon/deb/debian/changelog b/utils/packaging/daemon/deb/debian/changelog
new file mode 100644 (file)
index 0000000..3a4c641
--- /dev/null
@@ -0,0 +1,247 @@
+opensnitch (1.6.9-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Mon, 28 Apr 2025 01:31:50 +0200
+
+opensnitch (1.6.6-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Thu, 20 Jun 2024 00:02:56 +0200
+
+opensnitch (1.6.5-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Tue, 23 Jan 2024 21:25:33 +0100
+
+opensnitch (1.6.2-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Mon, 31 Jul 2023 00:32:11 +0200
+
+opensnitch (1.6.1-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Sun, 23 Jul 2023 22:13:33 +0200
+
+opensnitch (1.6.0.1-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Thu, 15 Jun 2023 23:44:15 +0200
+
+opensnitch (1.6.0-rc.5-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Sat, 18 Feb 2023 20:07:14 +0100
+
+opensnitch (1.6.0-rc.4-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Thu, 22 Dec 2022 10:07:14 +0100
+
+opensnitch (1.6.0-rc.3-1) unstable; urgency=medium
+
+  * eBPF modules are now installed under /usr/lib/opensnitchd/.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Thu, 08 Dec 2022 11:13:09 +0100
+
+opensnitch (1.6.0-rc.2-1) unstable; urgency=medium
+
+  * New eBPF module to receive events (new execs, etc).
+  * Allow to exclude connections from the events.
+  * more on github
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Thu, 14 Jul 2022 00:15:19 +0200
+
+opensnitch (1.6.0-rc.1-1) unstable; urgency=medium
+
+  * Allow to configure firewall from the GUI.
+  * New eBPF module to intercept outbound DNS requests.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Wed, 04 May 2022 00:55:54 +0200
+
+opensnitch (1.5.0-1) unstable; urgency=medium
+
+  * New release.
+  * Added Reject option.
+  * New lists types to block ads/malware/...
+  * Better connections interception.
+  * Better VPNs handling.
+  * Bug fixes.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Fri, 28 Jan 2022 23:20:38 +0100
+
+opensnitch (1.5.0~rc2-1) unstable; urgency=medium
+
+  * Better connections interception.
+  * Improvements.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Sun, 16 Jan 2022 23:15:12 +0100
+
+opensnitch (1.5.0~rc1-1) unstable; urgency=medium
+
+  * New features.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Thu, 07 Oct 2021 14:57:35 +0200
+
+opensnitch (1.4.0-1) unstable; urgency=medium
+
+  * final release.
+
+ -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com>  Fri, 27 Aug 2021 13:33:07 +0200
+
+opensnitch (1.4.0~rc4-1) unstable; urgency=medium
+
+  * Bug fix release.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Wed, 11 Aug 2021 15:17:49 +0200
+
+opensnitch (1.4.0~rc3-1) unstable; urgency=medium
+
+  * Bug fix release.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Fri, 16 Jul 2021 23:28:52 +0200
+
+opensnitch (1.4.0~rc2-1) unstable; urgency=medium
+
+  * Added eBPF support.
+  * Fixes and improvements.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Fri, 07 May 2021 01:08:02 +0200
+
+opensnitch (1.4.0~rc-1) unstable; urgency=medium
+
+  * Bug fix and improvements release.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Thu, 25 Mar 2021 01:02:31 +0100
+
+opensnitch (1.3.6-1) unstable; urgency=medium
+
+  * Bug fix and improvements release.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Wed, 10 Feb 2021 10:17:43 +0100
+
+opensnitch (1.3.5-1) unstable; urgency=medium
+
+  * Bug fix and improvements release.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Mon, 11 Jan 2021 18:01:53 +0100
+
+opensnitch (1.3.0-1) unstable; urgency=medium
+
+  * Fixed how we check rules
+  * Fixed cpu spike after disable interception.
+  * Fixed cleaning up fw rules on exit.
+  * make regexp rules case-insensitive by default
+  * allow to filter by dst network.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Wed, 16 Dec 2020 01:15:03 +0100
+
+opensnitch (1.3.0~rc-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Fri, 13 Nov 2020 00:51:34 +0100
+
+opensnitch (1.2.0-1) unstable; urgency=medium
+
+  * Fixed memleaks.
+  * Sort rules by name
+  * Added priority field to rules.
+  * Other fixes
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Mon, 09 Nov 2020 22:55:13 +0100
+
+opensnitch (1.0.1-1) unstable; urgency=medium
+
+  * Fixed app exit when IPv6 is not supported.
+  * Other fixes.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Thu, 30 Jul 2020 21:56:20 +0200
+
+opensnitch (1.0.0-1) unstable; urgency=medium
+
+  * v1.0.0 released.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Thu, 16 Jul 2020 00:19:26 +0200
+
+opensnitch (1.0.0rc11-1) unstable; urgency=medium
+
+  * Fixed multiple race conditions.
+  * Fixed CWD parsing when using audit proc monitor method.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Wed, 24 Jun 2020 00:10:38 +0200
+
+opensnitch (1.0.0rc10-1) unstable; urgency=medium
+
+  * Fixed checking UID functions availability.
+  * Improved process path parsing.
+  * Fixed applying config from the UI.
+  * Fixed default log level.
+  * Gather CWD and process environment vars.
+  * Increase default timeout when asking for a rule.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Sat, 13 Jun 2020 18:45:02 +0200
+
+opensnitch (1.0.0rc9-1) unstable; urgency=medium
+
+  * Ignore malformed rules from loading.
+  * Allow to modify and add rules from the UI.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Sun, 17 May 2020 18:18:24 +0200
+
+opensnitch (1.0.0rc8) unstable; urgency=medium
+
+  * Allow to change settings from the UI.
+  * Improved connection handling with the UI.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Wed, 29 Apr 2020 21:52:27 +0200
+
+opensnitch (1.0.0rc7-1) unstable; urgency=medium
+
+  * Stability, performance and realiability improvements.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Sun, 12 Apr 2020 23:25:41 +0200
+
+opensnitch (1.0.0rc6-1) unstable; urgency=medium
+
+  * Fixed iptables rules deletion.
+  * Improved PIDs cache.
+  * Added audit process monitoring method.
+  * Added logrotate file.
+  * Added default configuration file.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Sun, 08 Mar 2020 20:47:58 +0100
+
+opensnitch (1.0.0rc-5) unstable; urgency=medium
+
+  * Fixed netlink socket querying.
+  * Added check to reload firewall rules if missing.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Mon, 24 Feb 2020 19:55:06 +0100
+
+opensnitch (1.0.0rc-3) unstable; urgency=medium
+
+  * @see: https://github.com/gustavo-iniguez-goya/opensnitch/releases
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Tue, 18 Feb 2020 10:09:45 +0100
+
+opensnitch (1.0.0rc-2) unstable; urgency=medium
+
+  * UI minor changes
+  * Expand deb package compatibility.
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Wed, 05 Feb 2020 21:50:20 +0100
+
+opensnitch (1.0.0rc-1) unstable; urgency=medium
+
+  * Initial release
+
+ -- gustavo-iniguez-goya <gooffy1@gmail.com>  Fri, 22 Nov 2019 01:14:08 +0100
diff --git a/utils/packaging/daemon/deb/debian/control b/utils/packaging/daemon/deb/debian/control
new file mode 100644 (file)
index 0000000..c3ffc8b
--- /dev/null
@@ -0,0 +1,49 @@
+Source: opensnitch
+Maintainer: Debian Go Packaging Team <team+pkg-go@tracker.debian.org>
+Uploaders: Gustavo Iniguez Goya <gooffy@gmail.com>
+Section: devel
+Testsuite: autopkgtest-pkg-go
+Priority: optional
+Build-Depends:
+ debhelper-compat (= 11),
+ debhelper (>= 9),
+ dh-golang,
+ golang-any,
+ golang-github-vishvananda-netlink-dev,
+ golang-github-google-gopacket-dev,
+ golang-github-fsnotify-fsnotify-dev,
+ golang-golang-x-net-dev,
+ golang-google-grpc-dev,
+ golang-goprotobuf-dev,
+ pkg-config,
+ libnetfilter-queue-dev,
+ libmnl-dev
+Standards-Version: 4.4.0
+Vcs-Browser: https://salsa.debian.org/go-team/packages/opensnitch
+Vcs-Git: https://salsa.debian.org/go-team/packages/opensnitch.git
+Homepage: https://github.com/evilsocket/opensnitch
+Rules-Requires-Root: no
+XS-Go-Import-Path: github.com/evilsocket/opensnitch
+
+Package: opensnitch
+Section: net
+Architecture: any
+Depends:
+ libnetfilter-queue1, libc6, libnfnetlink0
+Built-Using: ${misc:Built-Using}
+Description: GNU/Linux interactive application firewall
+ OpenSnitch is a GNU/Linux firewall application.
+ Whenever a program makes a connection, it'll prompt the user to allow or deny
+ it.
+ .
+ The user can decide if block the outgoing connection based on properties of
+ the connection: by port, by uid, by dst ip, by program or a combination
+ of them.
+ .
+ These rules can last forever, until the app restart or just one time.
+ .
+ The GUI allows the user to view live outgoing connections, as well as search
+ by process, user, host or port.
+ .
+ OpenSnitch can also work as a system-wide domains blocker, by using lists
+ of domains, list of IPs or list of regular expressions.
diff --git a/utils/packaging/daemon/deb/debian/copyright b/utils/packaging/daemon/deb/debian/copyright
new file mode 100644 (file)
index 0000000..ee4ba23
--- /dev/null
@@ -0,0 +1,31 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Source: https://github.com/evilsocket/opensnitch
+Upstream-Name: opensnitch
+Files-Excluded:
+ Godeps/_workspace
+
+Files: *
+Copyright:
+ 2017-2018 evilsocket
+ 2019-2020 Gustavo Iñiguez Goia
+Comment: Debian packaging is licensed under the same terms as upstream
+License: GPL-3.0
+ This program is free software; you can redistribute it
+ and/or modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later
+ version.
+ .
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE.  See the GNU General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU General Public
+ License along with this program. If not,  If not, see 
+ http://www.gnu.org/licenses/.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ '/usr/share/common-licenses/GPL-3'.
diff --git a/utils/packaging/daemon/deb/debian/gbp.conf b/utils/packaging/daemon/deb/debian/gbp.conf
new file mode 100644 (file)
index 0000000..cec628c
--- /dev/null
@@ -0,0 +1,2 @@
+[DEFAULT]
+pristine-tar = True
diff --git a/utils/packaging/daemon/deb/debian/gitlab-ci.yml b/utils/packaging/daemon/deb/debian/gitlab-ci.yml
new file mode 100644 (file)
index 0000000..91ff7ea
--- /dev/null
@@ -0,0 +1,27 @@
+# auto-generated, DO NOT MODIFY.
+# The authoritative copy of this file lives at:
+# https://salsa.debian.org/go-team/ci/blob/master/config/gitlabciyml.go
+
+# TODO: publish under debian-go-team/ci
+image: stapelberg/ci2
+
+test_the_archive:
+  artifacts:
+    paths:
+    - before-applying-commit.json
+    - after-applying-commit.json
+  script:
+    # Create an overlay to discard writes to /srv/gopath/src after the build:
+    - "rm -rf /cache/overlay/{upper,work}"
+    - "mkdir -p /cache/overlay/{upper,work}"
+    - "mount -t overlay overlay -o lowerdir=/srv/gopath/src,upperdir=/cache/overlay/upper,workdir=/cache/overlay/work /srv/gopath/src"
+    - "export GOPATH=/srv/gopath"
+    - "export GOCACHE=/cache/go"
+    # Build the world as-is:
+    - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > before-applying-commit.json"
+    # Copy this package into the overlay:
+    - "GBP_CONF_FILES=:debian/gbp.conf gbp buildpackage --git-no-pristine-tar --git-ignore-branch --git-ignore-new --git-export-dir=/tmp/export --git-no-overlay --git-tarball-dir=/nonexistant --git-cleaner=/bin/true --git-builder='dpkg-buildpackage -S -d --no-sign'"
+    - "pgt-gopath -dsc /tmp/export/*.dsc"
+    # Rebuild the world:
+    - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > after-applying-commit.json"
+    - "ci-diff before-applying-commit.json after-applying-commit.json"
diff --git a/utils/packaging/daemon/deb/debian/opensnitch.init b/utils/packaging/daemon/deb/debian/opensnitch.init
new file mode 100644 (file)
index 0000000..77ce353
--- /dev/null
@@ -0,0 +1,78 @@
+#!/bin/sh
+
+### BEGIN INIT INFO
+# Provides:          opensnitchd
+# Required-Start:    $network $local_fs
+# Required-Stop:     $network $local_fs
+# Default-Start:     2 3 4 5
+# Default-Stop:      0 1 6
+# Short-Description: opensnitchd daemon
+# Description: opensnitch application firewall
+### END INIT INFO
+
+NAME=opensnitchd
+PIDDIR=/var/run/$NAME
+OPENSNITCHDPID=$PIDDIR/$NAME.pid
+
+# clear conflicting settings from the environment
+unset TMPDIR
+
+test -x /usr/bin/$NAME || exit 0
+
+. /lib/lsb/init-functions
+
+case $1 in
+    start)
+        log_daemon_msg "Starting opensnitch daemon" $NAME
+        if [ ! -d /etc/$NAME/rules ]; then
+            mkdir -p /etc/$NAME/rules &>/dev/null
+        fi
+
+        # Make sure we have our PIDDIR, even if it's on a tmpfs
+        install -o root -g root -m 755 -d $PIDDIR
+
+        if ! start-stop-daemon --start --quiet --oknodo --pidfile $OPENSNITCHDPID --background --exec /usr/bin/$NAME -- -rules-path /etc/$NAME/rules; then
+            log_end_msg 1
+            exit 1
+        fi
+
+        log_end_msg 0
+        ;;
+    stop)
+
+        log_daemon_msg "Stopping $NAME daemon" $NAME
+
+        start-stop-daemon --stop --quiet --signal QUIT --name $NAME
+        # Wait a little and remove stale PID file
+        sleep 1
+        if [ -f $OPENSNITCHDPID ] && ! ps h `cat $OPENSNITCHDPID` > /dev/null
+        then
+            rm -f $OPENSNITCHDPID
+        fi
+
+        log_end_msg 0
+
+        ;;
+    reload)
+        log_daemon_msg "Reloading $NAME" $NAME
+
+        start-stop-daemon --stop --quiet --signal HUP --pidfile $OPENSNITCHDPID
+
+        log_end_msg 0
+        ;;
+    restart|force-reload)
+        $0 stop
+        sleep 1
+        $0 start
+        ;;
+        status)
+        status_of_proc /usr/bin/$NAME $NAME
+        exit $?
+        ;;
+    *)
+        echo "Usage: /etc/init.d/opensnitchd {start|stop|reload|restart|force-reload|status}"
+        exit 1
+        ;;
+esac
+
+exit 0
diff --git a/utils/packaging/daemon/deb/debian/opensnitch.install b/utils/packaging/daemon/deb/debian/opensnitch.install
new file mode 100644 (file)
index 0000000..6c635c4
--- /dev/null
@@ -0,0 +1,5 @@
+daemon/default-config.json etc/opensnitchd/
+daemon/system-fw.json etc/opensnitchd/
+ebpf_prog/opensnitch.o usr/lib/opensnitchd/ebpf/
+ebpf_prog/opensnitch-dns.o usr/lib/opensnitchd/ebpf/
+ebpf_prog/opensnitch-procs.o usr/lib/opensnitchd/ebpf/
diff --git a/utils/packaging/daemon/deb/debian/opensnitch.logrotate b/utils/packaging/daemon/deb/debian/opensnitch.logrotate
new file mode 100644 (file)
index 0000000..7e1d486
--- /dev/null
@@ -0,0 +1,13 @@
+/var/log/opensnitchd.log {
+       rotate 7
+# order of the fields is important
+    maxsize 50M
+# we need this option in order to keep logging
+    copytruncate
+       missingok
+       notifempty
+       delaycompress
+       compress
+    create 640 root root
+       weekly
+}
diff --git a/utils/packaging/daemon/deb/debian/opensnitch.service b/utils/packaging/daemon/deb/debian/opensnitch.service
new file mode 100644 (file)
index 0000000..b4301a5
--- /dev/null
@@ -0,0 +1,17 @@
+[Unit]
+Description=Application firewall OpenSnitch
+Documentation=https://github.com/gustavo-iniguez-goya/opensnitch/wiki
+Wants=network.target
+After=network.target
+
+[Service]
+Type=simple
+PermissionsStartOnly=true
+ExecStartPre=/bin/mkdir -p /etc/opensnitchd/rules
+ExecStart=/usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules
+Restart=always
+RestartSec=30
+TimeoutStopSec=10
+
+[Install]
+WantedBy=multi-user.target
diff --git a/utils/packaging/daemon/deb/debian/rules b/utils/packaging/daemon/deb/debian/rules
new file mode 100755 (executable)
index 0000000..d7cacbb
--- /dev/null
@@ -0,0 +1,19 @@
+#!/usr/bin/make -f
+export DH_VERBOSE = 1
+export DESTDIR = debian/opensnitch
+
+override_dh_installsystemd:
+       dh_installsystemd --restart-after-upgrade
+
+execute_before_dh_auto_build:
+       cd proto; make ../daemon/ui/protocol/ui.pb.go
+
+execute_before_dh_auto_install:
+       mkdir -p $(DESTDIR)/usr/bin
+       mv _build/bin/daemon $(DESTDIR)/usr/bin/opensnitchd
+
+override_dh_auto_install:
+       dh_auto_install -- --no-source
+
+%:
+       dh $@ --builddirectory=_build --buildsystem=golang --with=golang
diff --git a/utils/packaging/daemon/deb/debian/source/format b/utils/packaging/daemon/deb/debian/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/utils/packaging/daemon/deb/debian/watch b/utils/packaging/daemon/deb/debian/watch
new file mode 100644 (file)
index 0000000..383dd73
--- /dev/null
@@ -0,0 +1,4 @@
+version=4
+opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/opensnitch-\$1\.tar\.gz/,\
+uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/ \
+  https://github.com/evilsocket/opensnitch/tags .*/v?(\d\S*)\.tar\.gz
diff --git a/utils/packaging/daemon/rpm/opensnitch.spec b/utils/packaging/daemon/rpm/opensnitch.spec
new file mode 100644 (file)
index 0000000..2497493
--- /dev/null
@@ -0,0 +1,100 @@
+Name:           opensnitch
+Version:        1.6.9
+Release:        1%{?dist}
+Summary:        OpenSnitch is a GNU/Linux interactive application firewall
+
+License:        GPLv3+
+URL:            https://github.com/evilsocket/%{name}
+Source0:        https://github.com/evilsocket/%{name}/releases/download/v%{version}/%{name}_%{version}.orig.tar.gz
+#BuildArch:     x86_64
+
+#BuildRequires:  godep
+Requires(post): info
+Requires(preun): info
+
+%description
+Whenever a program makes a connection, it'll prompt the user to allow or deny
+it.
+
+The user can decide if block the outgoing connection based on properties of
+the connection: by port, by uid, by dst ip, by program or a combination
+of them.
+
+These rules can last forever, until the app restart or just one time.
+
+The GUI allows the user to view live outgoing connections, as well as search
+by process, user, host or port.
+
+%prep
+rm -rf %{buildroot}
+
+%setup
+
+%build
+mkdir -p go/src/github.com/evilsocket
+ln -s $(pwd) go/src/github.com/evilsocket/opensnitch
+export GOPATH=$(pwd)/go
+cd go/src/github.com/evilsocket/opensnitch/
+make protocol
+cd go/src/github.com/evilsocket/opensnitch/daemon/
+go mod vendor
+go build -o opensnitchd .
+
+%install
+mkdir -p %{buildroot}/usr/bin/ %{buildroot}/usr/lib/opensnitchd/ebpf/ %{buildroot}/usr/lib/systemd/system/ %{buildroot}/etc/opensnitchd/rules %{buildroot}/etc/logrotate.d
+sed -i 's/\/usr\/local/\/usr/' daemon/opensnitchd.service
+install -m 755 daemon/opensnitchd %{buildroot}/usr/bin/opensnitchd
+install -m 644 daemon/opensnitchd.service %{buildroot}/usr/lib/systemd/system/opensnitch.service
+install -m 644 utils/packaging/daemon/deb/debian/opensnitch.logrotate %{buildroot}/etc/logrotate.d/opensnitch
+
+B=""
+if [ -f /etc/opensnitchd/default-config.json ]; then
+    B="-b"
+fi
+install -m 644 $B daemon/default-config.json %{buildroot}/etc/opensnitchd/default-config.json
+
+B=""
+if [ -f /etc/opensnitchd/system-fw.json ]; then
+    B="-b"
+fi
+install -m 644 $B daemon/system-fw.json %{buildroot}/etc/opensnitchd/system-fw.json
+
+install -m 644 ebpf_prog/opensnitch.o %{buildroot}/usr/lib/opensnitchd/ebpf/opensnitch.o
+install -m 644 ebpf_prog/opensnitch-dns.o %{buildroot}/usr/lib/opensnitchd/ebpf/opensnitch-dns.o
+install -m 644 ebpf_prog/opensnitch-procs.o %{buildroot}/usr/lib/opensnitchd/ebpf/opensnitch-procs.o
+
+# upgrade, uninstall
+%preun
+systemctl stop opensnitch.service || true
+
+%post
+if [ $1 -eq 1 ]; then
+    systemctl enable opensnitch.service
+fi
+systemctl start opensnitch.service
+
+# uninstall,upgrade
+%postun
+if [ $1 -eq 0 ]; then
+    systemctl disable opensnitch.service
+fi
+if [ $1 -eq 0 -a -f /etc/logrotate.d/opensnitch ]; then
+    rm /etc/logrotate.d/opensnitch
+fi
+
+# postun is the last step after reinstalling
+if [ $1 -eq 1 ]; then
+    systemctl start opensnitch.service
+fi
+
+%clean
+rm -rf %{buildroot}
+
+%files
+%config(noreplace) %{_sysconfdir}/opensnitchd/*
+%{_bindir}/opensnitchd
+%{_prefix}/lib/systemd/system/opensnitch.service
+%{_prefix}/lib/opensnitchd/ebpf/opensnitch.o
+%{_prefix}/lib/opensnitchd/ebpf/opensnitch-dns.o
+%{_prefix}/lib/opensnitchd/ebpf/opensnitch-procs.o
+%{_sysconfdir}/logrotate.d/opensnitch
diff --git a/utils/packaging/ui/deb/debian/changelog b/utils/packaging/ui/deb/debian/changelog
new file mode 100644 (file)
index 0000000..747f9a7
--- /dev/null
@@ -0,0 +1,275 @@
+opensnitch-ui (1.6.9-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Mon, 28 Apr 2025 01:32:23 +0200
+
+opensnitch-ui (1.6.8-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sat, 22 Feb 2025 11:24:32 +0100
+
+opensnitch-ui (1.6.7-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 25 Dec 2024 21:34:39 +0100
+
+opensnitch-ui (1.6.6-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 20 Jun 2024 00:03:33 +0200
+
+opensnitch-ui (1.6.5.1-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Fri, 09 Feb 2024 13:56:13 +0100
+
+opensnitch-ui (1.6.5-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Tue, 23 Jan 2024 21:26:13 +0100
+
+opensnitch-ui (1.6.4-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Mon, 06 Nov 2023 00:29:15 +0100
+
+opensnitch-ui (1.6.3-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 16 Aug 2023 22:19:51 +0200
+
+opensnitch-ui (1.6.2-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Mon, 31 Jul 2023 00:33:48 +0200
+
+opensnitch-ui (1.6.1-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sun, 23 Jul 2023 22:14:13 +0200
+
+opensnitch-ui (1.6.0.1-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 15 Jun 2023 23:44:50 +0200
+
+opensnitch-ui (1.6.0-rc.5-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sat, 18 Feb 2023 20:06:42 +0100
+
+opensnitch-ui (1.6.0-rc.4-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 22 Dec 2022 10:06:42 +0100
+
+opensnitch-ui (1.6.0-rc.3-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Tue, 15 Nov 2022 00:14:48 +0100
+
+opensnitch-ui (1.6.0-rc.2-1) unstable; urgency=medium
+
+  * Bugfixes.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 14 Jul 2022 00:20:00 +0200
+
+opensnitch-ui (1.6.0-rc.1-1) unstable; urgency=medium
+
+  * Allow to configure firewall from the GUI.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 04 May 2022 00:57:31 +0200
+
+opensnitch-ui (1.5.0-1) unstable; urgency=medium
+
+  * New release.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Fri, 28 Jan 2022 23:24:49 +0100
+
+opensnitch-ui (1.5.0~rc2-1) unstable; urgency=medium
+
+  * Performance improvements.
+  * Better sqlite3 support.
+  * Better system notifications.
+  * Better user experience.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sun, 16 Jan 2022 23:17:02 +0100
+
+opensnitch-ui (1.5.0~rc1-1) unstable; urgency=medium
+
+  * New features.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 07 Oct 2021 14:58:39 +0200
+
+opensnitch-ui (1.4.0-1) unstable; urgency=medium
+
+  * Final release.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Fri, 27 Aug 2021 13:35:06 +0200
+
+opensnitch-ui (1.4.0~rc4-1) unstable; urgency=medium
+
+  * UI improvements.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 11 Aug 2021 15:18:34 +0200
+
+opensnitch-ui (1.4.0~rc3-1) unstable; urgency=medium
+
+  * UI improvements.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Fri, 16 Jul 2021 23:30:47 +0200
+
+opensnitch-ui (1.4.0~rc2-1) unstable; urgency=medium
+
+  * Fixes and improvements.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Fri, 07 May 2021 01:08:51 +0200
+
+opensnitch-ui (1.4.0~rc-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 25 Mar 2021 01:03:57 +0100
+
+opensnitch-ui (1.3.6-1) unstable; urgency=medium
+
+  * Bug fix and improvements release.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 10 Feb 2021 10:17:43 +0100
+
+opensnitch-ui (1.3.5-1) unstable; urgency=medium
+
+  * Bug fix and improvements release.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Mon, 11 Jan 2021 18:02:35 +0100
+
+opensnitch-ui (1.3.0-1) unstable; urgency=medium
+
+  * Allow to filter by dst networks.
+  * Added check for configure showing pop-ups.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 16 Dec 2020 01:18:31 +0100
+
+opensnitch-ui (1.3.0~rc-1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Fri, 20 Nov 2020 13:32:07 +0100
+
+opensnitch-ui (1.2.0-1) unstable; urgency=medium
+
+  * Sort rules by name.
+  * Allow to set priority on rules.
+  * Rules are case-insensitive by default.
+  * Other fixes.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Mon, 09 Nov 2020 23:00:38 +0100
+
+opensnitch-ui (1.0.1-1) unstable; urgency=medium
+
+  * Fixed crash when clicking on General tab columns.
+  * Added literal DstHost to the pop-up combo box.
+  * Shorten autogenerated rules names.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Tue, 28 Jul 2020 23:43:15 +0200
+
+opensnitch-ui (1.0.0-1) unstable; urgency=medium
+
+  * v1.0.0 released.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 16 Jul 2020 00:20:19 +0200
+
+opensnitch-ui (1.0.0rc11-1) unstable; urgency=medium
+
+  * Added CWD field.
+  * Fixed columns resizing/restoring.
+  * Fixed General tab fields filtering.
+  * Pop-up window: display process path if it's hidden.
+  * Display better regexp errors on the rules editor.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 24 Jun 2020 00:20:57 +0200
+
+opensnitch-ui (1.0.0rc10-2) unstable; urgency=medium
+
+  * Fixed crash when selecting a user (closes #38).
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 17 Jun 2020 20:50:54 +0200
+
+opensnitch-ui (1.0.0rc10-1) unstable; urgency=medium
+
+  * Allow to filter data in all tabs.
+  * Refresh rules list after deleting a rule.
+  * Fixed high CPU usage while showing a notification.
+  * Fixed columns sort order.
+  * Allow to delete rules in batch.
+  * Remember the columns size.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sat, 13 Jun 2020 18:49:11 +0200
+
+opensnitch-ui (1.0.0rc9-1) unstable; urgency=medium
+
+  * Added rules editor dialog.
+  * Restart UI upon starting a new X session.
+  * Allow to configure max clients from the cli.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sun, 17 May 2020 18:19:38 +0200
+
+opensnitch-ui (1.0.0rc8) unstable; urgency=medium
+
+  * Allow to change settings (daemon && UI) from the UI.
+  * Added Nodes view.
+  * Improved UI performance, specially when remote nodes connected.
+  * Fixed race condition when adding stats of remote nodes.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Wed, 29 Apr 2020 21:56:54 +0200
+
+opensnitch-ui (1.0.0rc7-1) unstable; urgency=medium
+
+  * Added help menu.
+  * Added option to filter by command line.
+  * Fixed UI icons.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sun, 12 Apr 2020 23:49:13 +0200
+
+opensnitch-ui (1.0.0rc6-1) unstable; urgency=medium
+
+  * Fixed showing systray icon in Cinnamon.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Sun, 08 Mar 2020 20:50:52 +0100
+
+opensnitch-ui (1.0.0rc5-1) unstable; urgency=medium
+
+  * Workaround for crash parsing non-utf8 desktop files.
+  * Fixed crash loading sqlite driver.
+  * Fixed HighDpi scaling.
+  * Fixed prompt layout.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Mon, 24 Feb 2020 19:56:01 +0100
+
+opensnitch-ui (1.0.0rc3-1) unstable; urgency=medium
+
+  * Fixed regex patterns.
+  * Display alerts for not answered questions.
+  * Added option to allow/deny second level domains.
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Tue, 18 Feb 2020 10:14:59 +0100
+
+opensnitch-ui (1.0.0rc2-1) unstable; urgency=low
+
+  * initial release
+
+ -- Gustavo Iñiguez Goia <gooffy1@gmail.com>  Thu, 06 Feb 2020 00:20:02 +0100
diff --git a/utils/packaging/ui/deb/debian/compat b/utils/packaging/ui/deb/debian/compat
new file mode 100644 (file)
index 0000000..ec63514
--- /dev/null
@@ -0,0 +1 @@
+9
diff --git a/utils/packaging/ui/deb/debian/control b/utils/packaging/ui/deb/debian/control
new file mode 100644 (file)
index 0000000..5131d04
--- /dev/null
@@ -0,0 +1,48 @@
+Source: opensnitch-ui
+Maintainer: Gustavo Iñiguez Goia <gooffy1@gmail.com>
+Uploaders: Gustavo Iniguez Goya <gooffy@gmail.com>,
+Priority: optional
+Homepage: https://github.com/evilsocket/opensnitch
+Build-Depends:
+ qttools5-dev-tools,
+ python3-setuptools,
+ pyqt5-dev-tools,
+ python3-grpc-tools,
+ python3-all,
+ debhelper (>= 7.4.3),
+ dh-python
+Standards-Version: 3.9.1
+
+
+Package: python3-opensnitch-ui
+Architecture: all
+Section: net
+Depends:
+ netbase,
+ libqt5sql5-sqlite,
+ python3:any,
+ python3-six,
+ python3-pyqt5,
+ python3-pyqt5.qtsql,
+ python3-pyinotify,
+ python3-grpcio,
+ python3-protobuf,
+ python3-packaging,
+ python3-slugify,
+ python3-notify2,
+ xdg-user-dirs,
+ gtk-update-icon-cache
+Recommends: python3-pyasn
+Description: GNU/Linux interactive application firewall
+ opensnitch-ui is a GUI for opensnitch written in Python.
+ It allows the user to view live outgoing connections, as well as search
+ for details of the intercepted connections.
+ .
+ The user can decide if block outgoing connections based on properties of
+ the connection: by port, by uid, by dst ip, by program or a combination
+ of them.
+ .
+ These rules can last forever, until restart the daemon or just one time.
+ .
+ OpenSnitch can also work as a system-wide domains blocker, by using lists
+ of domains, list of IPs or list of regular expressions.
diff --git a/utils/packaging/ui/deb/debian/copyright b/utils/packaging/ui/deb/debian/copyright
new file mode 100644 (file)
index 0000000..4fa6f9c
--- /dev/null
@@ -0,0 +1,28 @@
+Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
+Source: https://github.com/evilsocket/opensnitch
+Upstream-Name: opensnitch-ui
+Files: *
+Copyright:
+ 2017-2018 evilsocket
+ 2019-2022 Gustavo Iñiguez Goia
+Comment: Debian packaging is licensed under the same terms as upstream
+License: GPL-3.0
+ This program is free software; you can redistribute it
+ and/or modify it under the terms of the GNU General Public
+ License as published by the Free Software Foundation; either
+ version 3 of the License, or (at your option) any later
+ version.
+ .
+ This program is distributed in the hope that it will be
+ useful, but WITHOUT ANY WARRANTY; without even the implied
+ warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
+ PURPOSE.  See the GNU General Public License for more
+ details.
+ .
+ You should have received a copy of the GNU General Public
+ License along with this program. If not,  If not, see 
+ http://www.gnu.org/licenses/.
+ .
+ On Debian systems, the full text of the GNU General Public
+ License version 3 can be found in the file
+ '/usr/share/common-licenses/GPL-3'.
diff --git a/utils/packaging/ui/deb/debian/postinst b/utils/packaging/ui/deb/debian/postinst
new file mode 100755 (executable)
index 0000000..a37c15a
--- /dev/null
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+set -e
+
+# https://github.com/evilsocket/opensnitch/issues/647
+wa_grpcio_647()
+{
+    badversion="1.30.2-3build6"
+    source /etc/os-release
+    if [ "$ID" = "linuxmint" -o "$ID" = "ubuntu" -o "$ID" = "pop" -o "$ID" = "elementary" -o "$ID" = "zorin" ]; then
+        v=$(dpkg-query -W -f '${Version}' python3-grpcio)
+        if [ "$v" = "$badversion" ]; then
+            echo
+            echo
+            echo "@@@@@@@@@@@@@@@@@@@ WARNING @@@@@@@@@@@@@@@@@@@@"
+            echo "    invalid python3-grpcio version installed"
+            echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
+            echo
+            echo "Installed python3-grpcio package ($badversion) has a bug which makes opensnitch UI unresponsive."
+            echo
+            echo "Launch opensnitch-ui, and if it consumes 100% of the CPU, try this:"
+            echo "~ $ sudo apt install python3-pip"
+            echo "~ $ pip3 install grpcio==1.41.0"
+            echo "~ $ pip3 install protobuf==3.20.0"
+            echo
+            echo "More information:"
+            echo " - https://bugs.launchpad.net/ubuntu/+source/grpc/+bug/1971114"
+            echo " - https://github.com/evilsocket/opensnitch/issues/647"
+            echo " - https://github.com/evilsocket/opensnitch/issues/1214#issuecomment-2518864350"
+            echo
+            echo
+        fi
+    fi
+}
+
+autostart_by_default()
+{
+    deskfile=/etc/xdg/autostart/opensnitch_ui.desktop
+    if [ -d /etc/xdg/autostart -a ! -h  $deskfile -a ! -f $deskfile ]; then
+        ln -s /usr/share/applications/opensnitch_ui.desktop /etc/xdg/autostart/
+    fi
+}
+
+autostart_by_default
+
+if command -v gtk-update-icon-cache >/dev/null && test -f /usr/share/icons/hicolor/index.theme ; then
+    gtk-update-icon-cache --quiet /usr/share/icons/hicolor/
+fi
+
+wa_grpcio_647
+
+#DEBHELPER#
diff --git a/utils/packaging/ui/deb/debian/postrm b/utils/packaging/ui/deb/debian/postrm
new file mode 100755 (executable)
index 0000000..8e0c129
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/sh
+set -e
+
+purge_files()
+{
+    for i in $(ls /home)
+    do
+        path=/home/$i/.config/
+        if [ -h $path/autostart/opensnitch_ui.desktop -o -f $path/autostart/opensnitch_ui.desktop ];then
+            rm -f $path/autostart/opensnitch_ui.desktop
+        fi
+        if [ -d $path/opensnitch/ ]; then
+            rm -rf $path/opensnitch/
+        fi
+    done
+
+    deskfile=/etc/xdg/autostart/opensnitch_ui.desktop
+    if [ -h $deskfile -o -f $deskfile ]; then
+        rm -f $deskfile
+    fi
+}
+
+pkill -15 opensnitch-ui || true
+
+case "$1" in
+    purge)
+        purge_files
+        ;;
+    remove)
+        purge_files
+        ;;
+esac
diff --git a/utils/packaging/ui/deb/debian/rules b/utils/packaging/ui/deb/debian/rules
new file mode 100755 (executable)
index 0000000..60aa6da
--- /dev/null
@@ -0,0 +1,26 @@
+#!/usr/bin/make -f
+
+# This file was automatically generated by stdeb 0.9.0 at
+# Thu, 06 Feb 2020 00:20:02 +0100
+
+%:
+       dh $@ --with python3 --buildsystem=python_distutils
+
+override_dh_auto_clean:
+       rm -f opensnitch/resources_rc.py
+       rm -rf opensnitch/i18n/
+       python3 setup.py clean -a
+       find . -name \*.pyc -exec rm {} \;
+
+override_dh_auto_build:
+       python3 setup.py build --force
+
+override_dh_auto_install:
+       cd i18n; make
+       cp -r i18n/locales/ opensnitch/i18n/
+       pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc
+       find opensnitch/proto/ -name 'ui_pb2_grpc.py' -exec sed -i 's/^import ui_pb2/from . import ui_pb2/' {} \;
+       python3 setup.py install --force --root=debian/python3-opensnitch-ui --no-compile -O0 --install-layout=deb  
+
+override_dh_python2:
+       dh_python2 --no-guessing-versions
diff --git a/utils/packaging/ui/deb/debian/source/format b/utils/packaging/ui/deb/debian/source/format
new file mode 100644 (file)
index 0000000..163aaf8
--- /dev/null
@@ -0,0 +1 @@
+3.0 (quilt)
diff --git a/utils/packaging/ui/deb/debian/source/options b/utils/packaging/ui/deb/debian/source/options
new file mode 100644 (file)
index 0000000..bcc4bbb
--- /dev/null
@@ -0,0 +1 @@
+extend-diff-ignore="\.egg-info$"
\ No newline at end of file
diff --git a/utils/packaging/ui/rpm/opensnitch-ui.spec b/utils/packaging/ui/rpm/opensnitch-ui.spec
new file mode 100644 (file)
index 0000000..ae677af
--- /dev/null
@@ -0,0 +1,91 @@
+%define name opensnitch-ui
+%define version 1.6.9
+%define unmangled_version 1.6.9
+%define release 1
+%define __python python3
+%define desktop_file opensnitch_ui.desktop
+
+Summary: Prompt service and UI for the OpenSnitch interactive application firewall.
+Name: %{name}
+Version: %{version}
+Release: %{release}
+Source0: %{name}-%{unmangled_version}.tar.gz
+License: GPL-3.0
+Group: Development/Libraries
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+Prefix: %{_prefix}
+BuildArch: noarch
+Vendor: OpenSnitch project
+Packager: Gustavo Iñiguez Goya <gooffy1@gmail.com>
+Url: https://github.com/evilsocket/opensnitch
+Requires: python3, python3-pip, (netcfg or setup), (python3-pyinotify or python3-inotify), python3-qt5
+Recommends: (python3-slugify or python3-python-slugify), python3-notify2, python3-protobuf >= 3.0, python3-grpcio >= 1.10.0, (qgnomeplatform-qt5 or QGnomePlatform-qt5), (python3-packaging or python-rpm-packaging)
+
+# avoid to depend on a particular python version
+%global __requires_exclude ^python\\(abi\\) = 3\\..$
+
+%description
+GUI for the opensnitch application firewall
+opensnitch-ui is a GUI for opensnitch written in Python.
+It allows the user to view live outgoing connections, as well as search
+to make connections.
+.
+The user can decide if block the outgoing connection based on properties of
+the connection: by port, by uid, by dst ip, by program or a combination
+of them.
+.
+These rules can last forever, until the app restart or just one time.
+
+%prep
+%setup -n %{name}-%{unmangled_version} -n %{name}-%{unmangled_version}
+
+%post
+
+if [ $1 -ge 1 ]; then
+    deskfile=/etc/xdg/autostart/opensnitch_ui.desktop
+    if [ -d /etc/xdg/autostart -a ! -h  $deskfile -a ! -f $deskfile ]; then
+        ln -s /usr/share/applications/opensnitch_ui.desktop /etc/xdg/autostart/
+    fi
+
+    gtk-update-icon-cache /usr/share/icons/hicolor/ || true
+fi
+
+%postun
+if [ $1 -eq 0 ]; then
+    # deprecated: kept for uninstalling old (<= v1.6.4) autostart files
+    for i in $(ls /home)
+    do
+        if grep /home/$i /etc/passwd &>/dev/null; then
+            path=/home/$i/.config/autostart/%{desktop_file}
+            if [ -h $path -o -f $path ]; then
+                rm -f $path
+            else
+                echo "[INFO] No desktop file for this user: $path"
+            fi
+        fi
+    done
+
+    deskfile=/etc/xdg/autostart/opensnitch_ui.desktop
+    if [ -h $deskfile -o -f $deskfile ]; then
+        rm -f $deskfile
+    fi
+
+    pkill -15 opensnitch-ui 2>/dev/null || true
+fi
+
+
+%build
+cd i18n; make; cd ..
+cp -r i18n/locales/ opensnitch/i18n
+pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc
+find opensnitch/proto/ -name 'ui_pb2_grpc.py' -exec sed -i 's/^import ui_pb2/from . import ui_pb2/' {} \;
+python3 setup.py build
+
+%install
+python3 setup.py install --install-lib=/usr/lib/python3/dist-packages/ --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --prefix=/usr --record=INSTALLED_FILES --install-scripts=/usr/bin
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files -f INSTALLED_FILES
+%defattr(-,root,root)
diff --git a/utils/scripts/ads/update_adlists.sh b/utils/scripts/ads/update_adlists.sh
new file mode 100755 (executable)
index 0000000..4f6049e
--- /dev/null
@@ -0,0 +1,106 @@
+#!/bin/bash
+# opensnitch - 2021-2022
+#
+# https://github.com/evilsocket/opensnitch/wiki/block-lists
+# 
+# Add the script to a regular user's crontab:
+# $ crontab -e
+# 0 11,17,23 * * * /home/user/scripts/update_adlists.sh
+
+# https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext
+# https://hostfiles.frogeye.fr/multiparty-trackers-hosts.txt
+# https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt
+# https://adaway.org/hosts.txt
+# https://www.github.developerdan.com/hosts/lists/tracking-aggressive-extended.txt
+# https://raw.githubusercontent.com/Kees1958/W3C_annual_most_used_survey_blocklist/master/TOP_EU_US_Ads_Trackers_HOST
+# https://curben.gitlab.io/malware-filter/urlhaus-filter-hosts.txt
+
+# Use any directory you want to save the lists.
+# If you use /etc/opensnitchd, give write permissions to blocklists/* for your user (chown -R /etc/opensnitchd/blocklists/).
+# or use a directory from your user's home.
+adsDir="/etc/opensnitchd/blocklists/domains/"
+
+# If you add new urls, remember to add the corresponding filename where it'll be save on disk.
+adsList=(
+    "https://malware-filter.gitlab.io/malware-filter/urlhaus-filter-hosts.txt"
+    "https://hostfiles.frogeye.fr/multiparty-trackers-hosts.txt"
+    "https://hostfiles.frogeye.fr/firstparty-trackers-hosts.txt"
+    "https://www.github.developerdan.com/hosts/lists/tracking-aggressive-extended.txt"
+    "https://adaway.org/hosts.txt"
+    "https://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&mimetype=plaintext")
+adsListNames=(
+    "urlhaus-filter-hosts.txt"
+    "multiparty-trackers-hosts.txt"
+    "firstparty-trackers-hosts.txt"
+    "tracking-aggressive-extended.txt"
+    "adaway-hosts.txt"
+    "yoyo-adservers.txt")
+
+function download_cname_trackers
+{
+    remoteSize=$(curl --silent -I https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/cname_trackers.txt | awk '/content-length:/ {print $2}'|tr -d '\r')
+    localSize=$(stat -c %s $adsDir/cname_trackers.txt)
+    if [ ! -z $remoteSize ]; then
+        if [ "$remoteSize" != "$localSize" ]; then
+            > /tmp/.cname_temp
+            cname_trackers=$(curl --silent https://raw.githubusercontent.com/AdguardTeam/AdguardFilters/master/SpywareFilter/sections/cname_trackers.txt | awk '/^!#include/ { print $2 }')
+            for tracker in $cname_trackers
+            do
+                curl --silent $tracker | grep "^||" | sed 's/^||\(.*\)^/0.0.0.0 \1/' >> /tmp/.cname_temp
+            done
+            mv /tmp/.cname_temp $adsDir/cname_trackers.txt
+        else
+            echo "[-] cname trackers not updated yet"
+        fi
+    fi
+}
+
+function download_list
+{
+    echo -n "[+] downloading new ads list... $1 -> $2 ($3, $4)"
+    curl --silent "$1" -o $2
+    [ $? -eq 0 ] && echo "   OK" || echo "   FAIL"
+}
+
+function download_ads_list
+{
+    reload=0
+    for idx in ${!adsList[@]}
+    do
+        echo "[+] Checking list ${adsList[$idx]}, ${adsListNames[$idx]}"
+        remoteSize=$(curl --silent -I ${adsList[$idx]}|awk '/content-length:/ {print $2}'|tr -d '\r')
+        localSize=$(stat -c %s $adsDir/${adsListNames[$idx]} 2>/dev/null)
+
+        if [ ! -z $remoteSize ]; then
+            if [ "$remoteSize" != "$localSize" ]; then
+                download_list "${adsList[$idx]}" "$adsDir/${adsListNames[$idx]}" $remoteSize $localSize
+                reload=1
+            else
+                echo "[-] ads list not updated yet:  $remoteSize, $localSize - ${adsList[$idx]}"
+            fi
+        else
+            echo "[!] No content-length header found: ${adsList[$idx]}"
+            echo "[.] Trying with Last-Modidifed"
+            lastMod=$(date +%s -d "$(curl --silent -I ${adsList[$idx]}|grep Last-Modified|cut -d: -f 2)")
+            localMod=$(stat -c %Y $adsDir/${adsListNames[$idx]} 2>/dev/null)
+            [ $? -eq 1 ] && localMod=0
+            if [ ! -z $lastMod -a $lastMod -gt $localMod ]; then
+                download_list "${adsList[$idx]}" "$adsDir/${adsListNames[$idx]}" $remoteSize $localSize
+            else
+                echo "[-] ads list not updated yet:  $lastMod, $localMod - ${adsList[$idx]}"
+            fi
+        fi
+    done
+}
+
+
+if [ ! -d $adsDir ]; then
+    mkdir -p $adsDir
+fi
+
+cd $adsDir
+
+download_ads_list
+download_cname_trackers
+
+echo "[~] Done"
diff --git a/utils/scripts/debug-ebpf-maps.sh b/utils/scripts/debug-ebpf-maps.sh
new file mode 100644 (file)
index 0000000..cd1287f
--- /dev/null
@@ -0,0 +1,94 @@
+#!/bin/sh
+#
+# OpenSnitch - 2023
+# https://github.com/evilsocket/opensnitch
+#
+# Usage: bash debug-ebpf-maps.sh tcp (or tcpv6, udp, udpv6)
+#
+
+function print_map_proto
+{
+    case "$1" in
+        12001)
+            echo "------------------------------  TCP  ------------------------------"
+            ;;
+        12002)
+            echo "------------------------------ TCPv6 ------------------------------"
+            ;;
+        12003)
+            echo "------------------------------  UDP  ------------------------------"
+            ;;
+        12004)
+            echo "------------------------------ UDPv6 ------------------------------"
+            ;;
+    esac
+}
+
+function dump_map
+{
+    echo
+    print_map_proto $mid
+    bpftool map dump id $1 |awk '
+    BEGIN { total=0; }
+    {
+        split($0, line);
+        if (line[1] == "key:"){
+            is_key=1;
+            total++;
+        } else if (is_key == 1){
+            sport=strtonum("0x" line[2] line[1]);
+            dport=strtonum("0x" line[7] line[8]);
+            printf("%d:%d.%d.%d.%d -> %d.%d.%d.%d:%d\n",
+                sport,
+                strtonum("0x" line[3]),
+                strtonum("0x" line[4]),
+                strtonum("0x" line[5]),
+                strtonum("0x" line[6]),
+                strtonum("0x" line[9]),
+                strtonum("0x" line[10]),
+                strtonum("0x" line[11]),
+                strtonum("0x" line[12]),
+                dport);
+            is_key=0;
+        }
+    }
+    END { printf("Total: %d\n", total); }'
+    print_map_proto $mid
+}
+
+if [ -z $1 ]; then
+    echo
+    echo "   Usage: bash debug-ebpf-maps.sh <proto> (tcp, tcpv6, udp or udpv6)"
+    echo
+    exit
+fi
+if ! command -v bpftool; then
+    echo
+    echo "  [error] bpftool not found. Install it."
+    echo
+    exit
+fi
+
+mid=0
+case "$1" in
+    tcp)
+        mid=$(bpftool map list | grep -B 1 12001 | grep hash | cut -d: -f1)
+        ;;
+    tcpv6)
+        mid=$(bpftool map list | grep -B 1 12002 | grep hash | cut -d: -f1)
+        ;;
+    udp)
+        mid=$(bpftool map list | grep -B 1 12003 | grep hash | cut -d: -f1)
+        ;;
+    udpv6)
+        mid=$(bpftool map list | grep -B 1 12004 | grep hash | cut -d: -f1)
+        ;;
+esac
+if [ $mid -eq 0 ]; then
+    echo
+    echo "  [error] Invalid protocol ($1)"
+    echo
+    exit
+fi
+
+dump_map $mid
diff --git a/utils/scripts/restart-opensnitch-onsleep.sh b/utils/scripts/restart-opensnitch-onsleep.sh
new file mode 100755 (executable)
index 0000000..ec0b6b1
--- /dev/null
@@ -0,0 +1,14 @@
+#!/bin/bash
+# opensnitch - 2022-2023
+#
+# Due to a bug in gobpf, when coming back from suspend state, ebpf stops working.
+# The temporal solution is to stop/start the daemon on suspend.
+#
+# Copy it to /lib/systemd/system-sleep/ with any name and exec permissions.
+#
+if [ "$1" == "pre" ]; then
+    service opensnitchd stop
+elif [ "$1" == "post" ]; then
+    service opensnitchd stop
+    service opensnitchd start
+fi