├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature-request.md └── workflows │ ├── build_ebpf_modules.yml │ ├── generic_validations.yml │ └── go.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── daemon ├── .gitignore ├── Gopkg.toml ├── Makefile ├── conman │ ├── connection.go │ └── connection_test.go ├── core │ ├── core.go │ ├── ebpf.go │ ├── gzip.go │ ├── system.go │ └── version.go ├── data │ └── rules │ │ ├── 000-allow-localhost.json │ │ └── 000-allow-localhost6.json ├── default-config.json ├── dns │ ├── ebpfhook.go │ ├── parse.go │ ├── systemd │ │ └── monitor.go │ └── track.go ├── firewall │ ├── common │ │ └── common.go │ ├── config │ │ ├── config.go │ │ └── config_test.go │ ├── iptables │ │ ├── iptables.go │ │ ├── monitor.go │ │ ├── rules.go │ │ └── system.go │ ├── nftables │ │ ├── chains.go │ │ ├── chains_test.go │ │ ├── exprs │ │ │ ├── counter.go │ │ │ ├── counter_test.go │ │ │ ├── ct.go │ │ │ ├── ct_test.go │ │ │ ├── enums.go │ │ │ ├── ether.go │ │ │ ├── ether_test.go │ │ │ ├── iface.go │ │ │ ├── iface_test.go │ │ │ ├── ip.go │ │ │ ├── ip_test.go │ │ │ ├── limit.go │ │ │ ├── log.go │ │ │ ├── log_test.go │ │ │ ├── meta.go │ │ │ ├── meta_test.go │ │ │ ├── nat.go │ │ │ ├── nat_test.go │ │ │ ├── notrack.go │ │ │ ├── operator.go │ │ │ ├── port.go │ │ │ ├── port_test.go │ │ │ ├── protocol.go │ │ │ ├── protocol_test.go │ │ │ ├── quota.go │ │ │ ├── quota_test.go │ │ │ ├── utils.go │ │ │ ├── verdict.go │ │ │ └── verdict_test.go │ │ ├── monitor.go │ │ ├── monitor_test.go │ │ ├── nftables.go │ │ ├── nftest │ │ │ ├── nftest.go │ │ │ ├── test_utils.go │ │ │ └── utils.go │ │ ├── parser.go │ │ ├── rule_helpers.go │ │ ├── rules.go │ │ ├── rules_test.go │ │ ├── system.go │ │ ├── system_test.go │ │ ├── tables.go │ │ ├── tables_test.go │ │ ├── testdata │ │ │ └── test-sysfw-conf.json │ │ ├── utils.go │ │ └── utils_test.go │ └── rules.go ├── go.mod ├── go.sum ├── log │ ├── formats │ │ ├── csv.go │ │ ├── formats.go │ │ ├── json.go │ │ ├── rfc3164.go │ │ └── rfc5424.go │ ├── log.go │ └── loggers │ │ ├── logger.go │ │ ├── remote.go │ │ ├── remote_syslog.go │ │ └── syslog.go ├── main.go ├── netfilter │ ├── packet.go │ ├── queue.c │ ├── queue.go │ └── queue.h ├── netlink │ ├── ifaces.go │ ├── procmon │ │ └── procmon.go │ ├── socket.go │ ├── socket_linux.go │ ├── socket_packet.go │ ├── socket_test.go │ └── socket_xdp.go ├── netstat │ ├── entry.go │ ├── find.go │ ├── parse.go │ └── parse_packet.go ├── network_aliases.json ├── opensnitchd-dinit ├── opensnitchd-openrc ├── opensnitchd.service ├── procmon │ ├── activepids.go │ ├── audit │ │ ├── client.go │ │ └── parse.go │ ├── cache.go │ ├── cache_events.go │ ├── cache_events_test.go │ ├── cache_test.go │ ├── details.go │ ├── ebpf │ │ ├── cache.go │ │ ├── config.go │ │ ├── debug.go │ │ ├── ebpf.go │ │ ├── events.go │ │ ├── find.go │ │ ├── monitor.go │ │ └── utils.go │ ├── find.go │ ├── find_test.go │ ├── monitor │ │ └── init.go │ ├── parse.go │ ├── process.go │ ├── process_test.go │ └── testdata │ │ └── proc-environ ├── rule │ ├── loader.go │ ├── loader_test.go │ ├── operator.go │ ├── operator_aliases.go │ ├── operator_lists.go │ ├── operator_test.go │ ├── rule.go │ ├── rule_test.go │ └── testdata │ │ ├── 000-allow-chrome.json │ │ ├── 001-deny-chrome.json │ │ ├── invalid-regexp-list.json │ │ ├── invalid-regexp.json │ │ ├── lists │ │ ├── domains │ │ │ └── domainlists.txt │ │ ├── ips │ │ │ └── ips.txt │ │ ├── nets │ │ │ └── nets.txt │ │ └── regexp │ │ │ └── domainsregexp.txt │ │ ├── live_reload │ │ ├── test-live-reload-delete.json │ │ └── test-live-reload-remove.json │ │ ├── rule-disabled-operator-list-expanded.json │ │ ├── rule-disabled-operator-list.json │ │ ├── rule-operator-list-data-empty.json │ │ └── rule-operator-list.json ├── statistics │ ├── event.go │ └── stats.go ├── system-fw.json ├── tasks │ ├── base.go │ ├── doc.go │ ├── main.go │ ├── main_test.go │ ├── nodemonitor │ │ ├── main.go │ │ └── main_test.go │ ├── pidmonitor │ │ ├── main.go │ │ └── main_test.go │ └── socketsmonitor │ │ ├── dump.go │ │ ├── main.go │ │ └── options.go └── ui │ ├── alerts.go │ ├── auth │ └── auth.go │ ├── client.go │ ├── client_test.go │ ├── config │ └── config.go │ ├── config_utils.go │ ├── notifications.go │ ├── notifications_tasks.go │ ├── protocol │ └── .gitkeep │ └── testdata │ ├── default-config.json │ └── default-config.json.orig ├── ebpf_prog ├── Makefile ├── README ├── arm-clang-asm-fix.patch ├── bpf_headers │ ├── bpf_core_read.h │ ├── bpf_helper_defs.h │ ├── bpf_helpers.h │ └── bpf_tracing.h ├── common.h ├── common_defs.h ├── opensnitch-dns.c ├── opensnitch-procs.c └── opensnitch.c ├── proto ├── .gitignore ├── Makefile └── ui.proto ├── release.sh ├── screenshots ├── opensnitch-ui-general-tab-deny.png ├── opensnitch-ui-proc-details.png └── screenshot.png ├── ui ├── .gitignore ├── LICENSE ├── MANIFEST.in ├── Makefile ├── bin │ └── opensnitch-ui ├── i18n │ ├── Makefile │ ├── README.md │ ├── generate_i18n.sh │ ├── locales │ │ ├── cs_CZ │ │ │ └── opensnitch-cs_CZ.ts │ │ ├── de_DE │ │ │ └── opensnitch-de_DE.ts │ │ ├── es_ES │ │ │ └── opensnitch-es_ES.ts │ │ ├── eu_ES │ │ │ └── opensnitch-eu_ES.ts │ │ ├── fi_FI │ │ │ └── opensnitch-fi_FI.ts │ │ ├── fr_FR │ │ │ └── opensnitch-fr_FR.ts │ │ ├── hi_IN │ │ │ └── opensnitch-hi_IN.ts │ │ ├── hu_HU │ │ │ └── opensnitch-hu_HU.ts │ │ ├── id_ID │ │ │ └── opensnitch-id_ID.ts │ │ ├── it_IT │ │ │ └── opensnitch-it_IT.ts │ │ ├── ja_JP │ │ │ └── opensnitch-ja_JP.ts │ │ ├── lt_LT │ │ │ └── opensnitch-lt_LT.ts │ │ ├── nb_NO │ │ │ └── opensnitch-nb_NO.ts │ │ ├── nl_NL │ │ │ └── opensnitch-nl_NL.ts │ │ ├── pt_BR │ │ │ └── opensnitch-pt_BR.ts │ │ ├── ro_RO │ │ │ └── opensnitch-ro_RO.ts │ │ ├── ru_RU │ │ │ └── opensnitch-ru_RU.ts │ │ ├── sv_SE │ │ │ └── opensnitch-sv_SE.ts │ │ ├── tr_TR │ │ │ └── opensnitch-tr_TR.ts │ │ ├── uk_UA │ │ │ └── opensnitch-uk_UA.ts │ │ └── zh_TW │ │ │ └── opensnitch-zh_TW.ts │ └── opensnitch_i18n.pro ├── opensnitch │ ├── __init__.py │ ├── actions │ │ ├── __init__.py │ │ ├── default_configs.py │ │ ├── enums.py │ │ └── utils.py │ ├── auth │ │ └── __init__.py │ ├── config.py │ ├── customwidgets │ │ ├── __init__.py │ │ ├── addresstablemodel.py │ │ ├── colorizeddelegate.py │ │ ├── firewalltableview.py │ │ ├── generictableview.py │ │ ├── main.py │ │ ├── netstattablemodel.py │ │ └── updownbtndelegate.py │ ├── database │ │ ├── __init__.py │ │ ├── enums.py │ │ └── migrations │ │ │ ├── upgrade_1.sql │ │ │ ├── upgrade_2.sql │ │ │ └── upgrade_3.sql │ ├── desktop_parser.py │ ├── dialogs │ │ ├── __init__.py │ │ ├── conndetails.py │ │ ├── firewall.py │ │ ├── firewall_rule.py │ │ ├── preferences.py │ │ ├── processdetails.py │ │ ├── prompt │ │ │ ├── __init__.py │ │ │ ├── _checksums.py │ │ │ ├── _constants.py │ │ │ ├── _details.py │ │ │ └── _utils.py │ │ ├── ruleseditor.py │ │ └── stats.py │ ├── firewall │ │ ├── __init__.py │ │ ├── chains.py │ │ ├── enums.py │ │ ├── exprs.py │ │ ├── profiles.py │ │ ├── rules.py │ │ └── utils.py │ ├── nodes.py │ ├── notifications.py │ ├── plugins │ │ ├── __init__.py │ │ ├── downloader │ │ │ ├── __init__.py │ │ │ ├── _gui.py │ │ │ ├── downloader.py │ │ │ └── example │ │ │ │ └── downloaders.json │ │ ├── highlight │ │ │ ├── __init__.py │ │ │ ├── example │ │ │ │ ├── commonActionsDelegate.json │ │ │ │ └── rulesActionsDelegate.json │ │ │ └── highlight.py │ │ ├── sample │ │ │ ├── __init__.py │ │ │ └── sample.py │ │ └── virustotal │ │ │ ├── __init__.py │ │ │ ├── _popups.py │ │ │ ├── _procdialog.py │ │ │ ├── _utils.py │ │ │ ├── example │ │ │ └── virustotal.json │ │ │ └── virustotal.py │ ├── proto │ │ ├── __init__.py │ │ ├── pre3200 │ │ │ ├── ui_pb2.py │ │ │ └── ui_pb2_grpc.py │ │ ├── ui_pb2.py │ │ └── ui_pb2_grpc.py │ ├── res │ │ ├── __init__.py │ │ ├── firewall.ui │ │ ├── firewall_rule.ui │ │ ├── icon-alert.png │ │ ├── icon-off.png │ │ ├── icon-pause.png │ │ ├── icon-pause.svg │ │ ├── icon-red.png │ │ ├── icon-white.png │ │ ├── icon-white.svg │ │ ├── icon.png │ │ ├── preferences.ui │ │ ├── process_details.ui │ │ ├── prompt.ui │ │ ├── resources.qrc │ │ ├── ruleseditor.ui │ │ ├── stats.ui │ │ ├── terminal.svg │ │ └── themes │ │ │ └── dark │ │ │ └── icons │ │ │ ├── LICENSE │ │ │ ├── accessories-text-editor.svg │ │ │ ├── application-exit.svg │ │ │ ├── applications-system.svg │ │ │ ├── computer.svg │ │ │ ├── dialog-cancel.svg │ │ │ ├── dialog-close.svg │ │ │ ├── dialog-error.svg │ │ │ ├── dialog-information.svg │ │ │ ├── dialog-ok.svg │ │ │ ├── dialog-warning.svg │ │ │ ├── document-new.svg │ │ │ ├── document-open.svg │ │ │ ├── document-properties.svg │ │ │ ├── document-save.svg │ │ │ ├── edit-clear-all.svg │ │ │ ├── edit-clear.svg │ │ │ ├── edit-delete.svg │ │ │ ├── emblem-default.svg │ │ │ ├── emblem-documents.svg │ │ │ ├── emblem-important.svg │ │ │ ├── emblem-system.svg │ │ │ ├── folder-remote.svg │ │ │ ├── format-justify-fill.svg │ │ │ ├── go-bottom.svg │ │ │ ├── go-down.svg │ │ │ ├── go-jump.svg │ │ │ ├── go-previous.svg │ │ │ ├── go-top.svg │ │ │ ├── go-up.svg │ │ │ ├── help-browser.svg │ │ │ ├── list-add.svg │ │ │ ├── list-remove.svg │ │ │ ├── media-playback-pause.svg │ │ │ ├── media-playback-start.svg │ │ │ ├── network-server.svg │ │ │ ├── network-wired.svg │ │ │ ├── network-workgroup.svg │ │ │ ├── preferences-desktop.svg │ │ │ ├── preferences-system.svg │ │ │ ├── reload.svg │ │ │ ├── security-high.svg │ │ │ ├── system-run.svg │ │ │ ├── system-search.svg │ │ │ ├── view-sort-ascending.svg │ │ │ ├── view-sort-descending.svg │ │ │ └── window-close.svg │ ├── rules.py │ ├── service.py │ ├── utils │ │ ├── __init__.py │ │ ├── duration │ │ │ ├── __init__.py │ │ │ └── duration.py │ │ ├── infowindow.py │ │ ├── languages.py │ │ ├── network_aliases │ │ │ ├── __init__.py │ │ │ ├── network_aliases.json │ │ │ └── network_aliases.py │ │ ├── qvalidator.py │ │ ├── sockets.py │ │ └── xdg.py │ └── version.py ├── requirements.txt ├── resources │ ├── icons │ │ ├── 48x48 │ │ │ └── opensnitch-ui.png │ │ ├── 64x64 │ │ │ └── opensnitch-ui.png │ │ └── opensnitch-ui.svg │ ├── io.github.evilsocket.opensnitch.appdata.xml │ ├── kcm_opensnitch.desktop │ └── opensnitch_ui.desktop ├── setup.py └── tests │ ├── README.md │ ├── __init__.py │ ├── dialogs │ ├── __init__.py │ ├── test_preferences.py │ └── test_ruleseditor.py │ └── test_nodes.py └── utils ├── legacy └── make_ads_rules.py ├── packaging ├── build_modules.sh ├── daemon │ ├── deb │ │ └── debian │ │ │ ├── NEWS │ │ │ ├── changelog │ │ │ ├── control │ │ │ ├── copyright │ │ │ ├── gbp.conf │ │ │ ├── gitlab-ci.yml │ │ │ ├── opensnitch.init │ │ │ ├── opensnitch.install │ │ │ ├── opensnitch.logrotate │ │ │ ├── opensnitch.service │ │ │ ├── rules │ │ │ ├── source │ │ │ └── format │ │ │ └── watch │ └── rpm │ │ └── opensnitch.spec └── ui │ ├── deb │ └── debian │ │ ├── changelog │ │ ├── compat │ │ ├── control │ │ ├── copyright │ │ ├── postinst │ │ ├── postrm │ │ ├── rules │ │ └── source │ │ ├── format │ │ └── options │ └── rpm │ └── opensnitch-ui.spec └── scripts ├── ads └── update_adlists.sh ├── debug-ebpf-maps.sh ├── ipasn_db_sync.sh ├── ipasn_db_update.sh └── restart-opensnitch-onsleep.sh /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: gustavo-iniguez-goya 4 | patreon: # Replace with a single patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 🐞 Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug Report] ' 5 | labels: 'bug' 6 | assignees: '' 7 | 8 | --- 9 | 10 | <!-- 11 | Please, check the FAQ and Known Problems pages before creating the bug report: 12 | 13 | https://github.com/evilsocket/opensnitch/wiki/FAQs 14 | 15 | GUI related issues: 16 | https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems 17 | 18 | Daemon related issues: 19 | - Run `opensnitchd -check-requirements` to see if your kernel is compatible. 20 | - https://github.com/evilsocket/opensnitch/wiki/daemon-known-problems 21 | --> 22 | 23 | ### Describe the bug: 24 | <!-- A clear and concise description of what the bug is. --> 25 | 26 | Include the following information: 27 | - OpenSnitch version: 28 | - OS: [e.g. Debian GNU/Linux, ArchLinux, Slackware, ...] 29 | - OS version: [e.g. Buster, 10.3, 20.04] 30 | - Window Manager: [e.g. GNOME Shell, KDE, enlightenment, i3wm, ...] 31 | - Kernel version: [`echo $(uname -a)`] 32 | 33 | ### To Reproduce: 34 | <!-- Describe in detail as much as you can what happened. --> 35 | 36 | Steps to reproduce the behavior: 37 | 1. Go to '...' 38 | 2. Click on '....' 39 | 3. Scroll down to '....' 40 | 4. See error 41 | 42 | ### Post error logs: 43 | <!-- 44 | If it's a crash of the GUI: 45 | - Launch it from a terminal and reproduce the issue. 46 | - Post the errors logged to the terminal. 47 | 48 | If the daemon doesn't start or doesn't intercept connections: 49 | - Run `opensnitchd -check-requirements` to see if your kernel is compatible. 50 | - Post last 15 lines of the log file `/var/log/opensnitchd.log` 51 | - Or launch it from a terminal as root (`# /usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules`) and post the errors logged to the terminal. 52 | 53 | If the deb or rpm packages fail to install: 54 | - Install them from a terminal (`$ sudo dpkg -i opensnitch*` / `$ sudo yum install opensnitch*`), and post the errors logged to stdout. 55 | --> 56 | 57 | ### Expected behavior (optional): 58 | <!-- A clear and concise description of what you expected to happen. --> 59 | 60 | ### Screenshots: 61 | <!-- If applicable, add screenshots or videos to help explain your problem. It may help to understand the issue much better. --> 62 | 63 | ### Additional context: 64 | <!-- Add any other context about the problem here. --> 65 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: 🙋 Question 3 | url: https://github.com/evilsocket/opensnitch/discussions/new 4 | about: Ask your question here 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: 💡 Feature request 3 | about: Suggest an idea 4 | title: '[Feature Request] <title>' 5 | labels: feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | <!-- 11 | Note: Please, use the search box to see if this feature has already been requested. 12 | --> 13 | 14 | ### Summary: 15 | <!-- A concise description of the new feature. --> 16 | -------------------------------------------------------------------------------- /.github/workflows/build_ebpf_modules.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI - build eBPF modules 4 | 5 | # Controls when the workflow will run 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the "master" branch 8 | push: 9 | paths: 10 | - 'ebpf_prog/*' 11 | - '.github/workflows/build_ebpf_modules.yml' 12 | pull_request: 13 | paths: 14 | - 'ebpf_prog/*' 15 | - '.github/workflows/build_ebpf_modules.yml' 16 | 17 | # Allows you to run this workflow manually from the Actions tab 18 | workflow_dispatch: 19 | 20 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 21 | jobs: 22 | 23 | # This workflow contains a single job called "build" 24 | # The matrix configuration will execute the steps, once per dimension defined: 25 | # kernel 5.8 + tag 1.5.0 26 | # kernel 5.8 + tag master 27 | # kernel 6.0 + tag 1.5.0, etc 28 | build: 29 | strategy: 30 | matrix: 31 | kernel: ["6.0"] 32 | tag: ["1.5.0", "master"] 33 | 34 | runs-on: ubuntu-22.04 35 | 36 | steps: 37 | - name: Check out code into the Go module directory 38 | uses: actions/checkout@v3 39 | with: 40 | # ref: can be a branch name, tag, commit, etc 41 | ref: ${{ matrix.tag }} 42 | 43 | - name: Get dependencies 44 | run: | 45 | 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) 46 | - name: Download kernel sources and compile eBPF modules 47 | run: | 48 | kernel_version="${{ matrix.kernel }}" 49 | if [ ! -d utils/packaging/ ]; then 50 | mkdir -p utils/packaging/ 51 | fi 52 | wget https://raw.githubusercontent.com/evilsocket/opensnitch/master/utils/packaging/build_modules.sh -O utils/packaging/build_modules.sh 53 | bash utils/packaging/build_modules.sh $kernel_version 54 | sha1sum ebpf_prog/modules/opensnitch*o > ebpf_prog/modules/checksums.txt 55 | 56 | - uses: actions/upload-artifact@v4 57 | with: 58 | name: opensnitch-ebpf-modules-${{ matrix.kernel }}-${{ matrix.tag }} 59 | path: ebpf_prog/modules/* 60 | -------------------------------------------------------------------------------- /.github/workflows/generic_validations.yml: -------------------------------------------------------------------------------- 1 | name: Test resources validation 2 | on: 3 | 4 | # Trigger this workflow only when ebpf modules changes. 5 | push: 6 | paths: 7 | - 'ui/resources/*' 8 | - '.github/workflows/generic.yml' 9 | pull_request: 10 | paths: 11 | - 'ui/resources/*' 12 | - '.github/workflows/generic.yml' 13 | 14 | # Allow to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | jobs: 18 | 19 | build: 20 | name: Install tools 21 | runs-on: ubuntu-latest 22 | steps: 23 | 24 | - name: Check out git code 25 | uses: actions/checkout@v2 26 | 27 | - name: Get and prepare dependencies 28 | run: | 29 | set -e 30 | set -x 31 | sudo apt install desktop-file-utils appstream 32 | - name: Validate resources 33 | run: | 34 | set -e 35 | set -x 36 | desktop-file-validate ui/resources/opensnitch_ui.desktop 37 | appstreamcli validate ui/resources/io.github.evilsocket.opensnitch.appdata.xml 38 | -------------------------------------------------------------------------------- /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Build status 2 | on: 3 | push: 4 | paths: 5 | - 'daemon/**' 6 | - '.github/workflows/go.yml' 7 | pull_request: 8 | paths: 9 | - 'daemon/**' 10 | - '.github/workflows/go.yml' 11 | 12 | # Allows you to run this workflow manually from the Actions tab 13 | workflow_dispatch: 14 | 15 | jobs: 16 | build: 17 | name: Build 18 | runs-on: ubuntu-latest 19 | steps: 20 | - name: Set up Go 1.21.13 21 | uses: actions/setup-go@v3 22 | with: 23 | go-version: 1.21.13 24 | id: go 25 | 26 | - name: Check out code into the Go module directory 27 | uses: actions/checkout@v3 28 | 29 | - name: Get dependencies 30 | run: | 31 | sudo apt-get install git libnetfilter-queue-dev libmnl-dev libpcap-dev protobuf-compiler 32 | export GOPATH=~/go 33 | export PATH=$PATH:$GOPATH/bin 34 | go install github.com/golang/protobuf/protoc-gen-go@latest 35 | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.1 36 | go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.3.0 37 | cd proto 38 | make ../daemon/ui/protocol/ui.pb.go 39 | cd ../daemon 40 | go mod tidy; go mod vendor 41 | 42 | - name: Build 43 | run: | 44 | cd daemon 45 | go build -v . 46 | - name: Test 47 | run: | 48 | cd daemon 49 | sudo PRIVILEGED_TESTS=1 NETLINK_TESTS=1 go test ./... 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sock 2 | *.pyc 3 | *.profile 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: protocol opensnitch_daemon gui 2 | 3 | install: 4 | @cd daemon && make install 5 | @cd ui && make install 6 | 7 | protocol: 8 | @cd proto && make 9 | 10 | opensnitch_daemon: 11 | @cd daemon && make 12 | 13 | gui: 14 | @cd ui && make 15 | 16 | clean: 17 | @cd daemon && make clean 18 | @cd proto && make clean 19 | @cd ui && make clean 20 | 21 | run: 22 | cd ui && pip3 install --upgrade . && cd .. 23 | opensnitch-ui --socket unix:///tmp/osui.sock & 24 | ./daemon/opensnitchd -rules-path /etc/opensnitchd/rules -ui-socket unix:///tmp/osui.sock -cpu-profile cpu.profile -mem-profile mem.profile 25 | 26 | test: 27 | clear 28 | make clean 29 | clear 30 | mkdir -p rules 31 | make 32 | clear 33 | make run 34 | 35 | adblocker: 36 | clear 37 | make clean 38 | clear 39 | make 40 | clear 41 | python make_ads_rules.py 42 | clear 43 | cd ui && pip3 install --upgrade . && cd .. 44 | opensnitch-ui --socket unix:///tmp/osui.sock & 45 | ./daemon/opensnitchd -rules-path /etc/opensnitchd/rules -ui-socket unix:///tmp/osui.sock 46 | 47 | 48 | -------------------------------------------------------------------------------- /daemon/.gitignore: -------------------------------------------------------------------------------- 1 | opensnitchd 2 | vendor 3 | -------------------------------------------------------------------------------- /daemon/Gopkg.toml: -------------------------------------------------------------------------------- 1 | [[constraint]] 2 | name = "github.com/fsnotify/fsnotify" 3 | version = "1.4.7" 4 | 5 | [[constraint]] 6 | name = "github.com/google/gopacket" 7 | version = "~1.1.14" 8 | 9 | [[constraint]] 10 | name = "google.golang.org/grpc" 11 | version = "~1.11.2" 12 | 13 | [[constraint]] 14 | name = "github.com/evilsocket/ftrace" 15 | version = "~1.2.0" 16 | 17 | [prune] 18 | go-tests = true 19 | unused-packages = true 20 | -------------------------------------------------------------------------------- /daemon/Makefile: -------------------------------------------------------------------------------- 1 | #SRC contains all *.go *.c *.h files in daemon/ and its subfolders 2 | SRC := $(shell find . -type f -name '*.go' -o -name '*.h' -o -name '*.c') 3 | PREFIX?=/usr/local 4 | 5 | all: opensnitchd 6 | 7 | install: 8 | @mkdir -p $(DESTDIR)/etc/opensnitchd/rules 9 | @install -Dm755 opensnitchd \ 10 | -t $(DESTDIR)$(PREFIX)/bin/ 11 | @install -Dm644 opensnitchd.service \ 12 | -t $(DESTDIR)/etc/systemd/system/ 13 | @install -Dm644 default-config.json \ 14 | -t $(DESTDIR)/etc/opensnitchd/ 15 | @install -Dm644 system-fw.json \ 16 | -t $(DESTDIR)/etc/opensnitchd/ 17 | @install -Dm644 network_aliases.json \ 18 | -t $(DESTDIR)/etc/opensnitchd/ 19 | @systemctl daemon-reload 20 | 21 | opensnitchd: $(SRC) 22 | @go get 23 | @go build -o opensnitchd . 24 | 25 | clean: 26 | @rm -rf opensnitchd 27 | 28 | 29 | -------------------------------------------------------------------------------- /daemon/core/core.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "os/exec" 7 | "os/user" 8 | "path/filepath" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | const ( 14 | defaultTrimSet = "\r\n\t " 15 | ) 16 | 17 | // Trim remove trailing spaces from a string. 18 | func Trim(s string) string { 19 | return strings.Trim(s, defaultTrimSet) 20 | } 21 | 22 | // Exec spawns a new process and reurns the output. 23 | func Exec(executable string, args []string) (string, error) { 24 | path, err := exec.LookPath(executable) 25 | if err != nil { 26 | return "", err 27 | } 28 | 29 | raw, err := exec.Command(path, args...).CombinedOutput() 30 | if err != nil { 31 | return "", err 32 | } 33 | return Trim(string(raw)), nil 34 | } 35 | 36 | // Exists checks if a path exists. 37 | func Exists(path string) bool { 38 | if _, err := os.Stat(path); os.IsNotExist(err) { 39 | return false 40 | } 41 | return true 42 | } 43 | 44 | // ExpandPath replaces '~' shorthand with the user's home directory. 45 | func ExpandPath(path string) (string, error) { 46 | // Check if path is empty 47 | if path != "" { 48 | if strings.HasPrefix(path, "~") { 49 | usr, err := user.Current() 50 | if err != nil { 51 | return "", err 52 | } 53 | // Replace only the first occurrence of ~ 54 | path = strings.Replace(path, "~", usr.HomeDir, 1) 55 | } 56 | return filepath.Abs(path) 57 | } 58 | return "", nil 59 | } 60 | 61 | // IsAbsPath verifies if a path is absolute or not 62 | func IsAbsPath(path string) bool { 63 | return path[0] == 47 // 47 == '/' 64 | } 65 | 66 | // GetFileModTime checks if a file has been modified. 67 | func GetFileModTime(filepath string) (time.Time, error) { 68 | fi, err := os.Stat(filepath) 69 | if err != nil || fi.IsDir() { 70 | return time.Now(), fmt.Errorf("GetFileModTime() Invalid file") 71 | } 72 | return fi.ModTime(), nil 73 | } 74 | 75 | // ConcatStrings joins the provided strings. 76 | func ConcatStrings(args ...string) string { 77 | return strings.Join(args, "") 78 | } 79 | -------------------------------------------------------------------------------- /daemon/core/ebpf.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/cilium/ebpf" 7 | "github.com/evilsocket/opensnitch/daemon/log" 8 | ) 9 | 10 | // LoadEbpfModule loads the given eBPF module, from the given path if specified. 11 | // Otherwise t'll try to load the module from several default paths. 12 | func LoadEbpfModule(module, path string) (m *ebpf.Collection, err error) { 13 | var ( 14 | modulesDir = "/opensnitchd/ebpf" 15 | paths = []string{ 16 | fmt.Sprint("/usr/local/lib", modulesDir), 17 | fmt.Sprint("/usr/lib", modulesDir), 18 | fmt.Sprint("/etc/opensnitchd"), // Deprecated: will be removed in future versions. 19 | } 20 | ) 21 | 22 | // if path has been specified, try to load the module from there. 23 | if path != "" { 24 | paths = []string{path} 25 | } 26 | 27 | modulePath := "" 28 | moduleError := fmt.Errorf(`Module not found (%s) in any of the paths. 29 | You may need to install the corresponding package`, module) 30 | 31 | logLevel := ebpf.LogLevel(0) 32 | if log.GetLogLevel() == log.DEBUG { 33 | logLevel = (ebpf.LogLevelBranch | ebpf.LogLevelInstruction | ebpf.LogLevelStats) 34 | } 35 | collOpts := ebpf.CollectionOptions{ 36 | Programs: ebpf.ProgramOptions{LogLevel: logLevel}, 37 | } 38 | 39 | for _, p := range paths { 40 | modulePath = fmt.Sprint(p, "/", module) 41 | log.Debug("[eBPF] trying to load %s", modulePath) 42 | if !Exists(modulePath) { 43 | continue 44 | } 45 | specs, err := ebpf.LoadCollectionSpec(modulePath) 46 | if err != nil { 47 | log.Error("[eBPF] module specs error: %s", err) 48 | continue 49 | } 50 | m, err := ebpf.NewCollectionWithOptions(specs, collOpts) 51 | if err != nil { 52 | log.Error("[eBPF] module collection error: %s", err) 53 | continue 54 | } 55 | 56 | log.Info("[eBPF] module loaded: %s", modulePath) 57 | return m, nil 58 | } 59 | moduleError = fmt.Errorf(` 60 | unable to load eBPF module (%s). Your kernel version (%s) might not be compatible. 61 | If this error persists, change process monitor method to 'proc'`, module, GetKernelVersion()) 62 | 63 | return m, moduleError 64 | } 65 | -------------------------------------------------------------------------------- /daemon/core/gzip.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import ( 4 | "compress/gzip" 5 | "io/ioutil" 6 | "os" 7 | ) 8 | 9 | // ReadGzipFile reads a gzip to text. 10 | func ReadGzipFile(filename string) ([]byte, error) { 11 | fd, err := os.Open(filename) 12 | if err != nil { 13 | return nil, err 14 | } 15 | defer fd.Close() 16 | 17 | gz, err := gzip.NewReader(fd) 18 | if err != nil { 19 | return nil, err 20 | } 21 | defer gz.Close() 22 | 23 | s, err := ioutil.ReadAll(gz) 24 | if err != nil { 25 | return nil, err 26 | } 27 | return s, nil 28 | } 29 | -------------------------------------------------------------------------------- /daemon/core/version.go: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | // version related consts 4 | const ( 5 | Name = "opensnitch-daemon" 6 | Version = "1.7.0" 7 | Author = "Simone 'evilsocket' Margaritelli" 8 | Website = "https://github.com/evilsocket/opensnitch" 9 | ) 10 | -------------------------------------------------------------------------------- /daemon/data/rules/000-allow-localhost.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2025-04-09T23:21:35-06:00", 3 | "updated": "2025-04-09T23:21:35-06:00", 4 | "name": "000-allow-localhost", 5 | "description": "Allow connections to localhost. See this link for more information:\nhttps://github.com/evilsocket/opensnitch/wiki/Rules#localhost-connections", 6 | "action": "allow", 7 | "duration": "always", 8 | "operator": { 9 | "operand": "dest.network", 10 | "data": "127.0.0.0/8", 11 | "type": "network", 12 | "list": [], 13 | "sensitive": false 14 | }, 15 | "enabled": true, 16 | "precedence": true, 17 | "nolog": true 18 | } 19 | -------------------------------------------------------------------------------- /daemon/data/rules/000-allow-localhost6.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2025-04-09T23:17:39-06:00", 3 | "updated": "2025-04-09T23:17:39-06:00", 4 | "name": "000-allow-localhost6", 5 | "description": "Allow connections to localhost. See this link for more information:\nhttps://github.com/evilsocket/opensnitch/wiki/Rules#localhost-connections", 6 | "action": "allow", 7 | "duration": "always", 8 | "operator": { 9 | "operand": "dest.network", 10 | "data": "::1/128", 11 | "type": "network", 12 | "list": [], 13 | "sensitive": false 14 | }, 15 | "enabled": true, 16 | "precedence": true, 17 | "nolog": true 18 | } 19 | -------------------------------------------------------------------------------- /daemon/default-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "Server": 3 | { 4 | "Address":"unix:///tmp/osui.sock", 5 | "Authentication": { 6 | "Type": "simple" 7 | }, 8 | "LogFile":"/var/log/opensnitchd.log" 9 | }, 10 | "DefaultAction": "allow", 11 | "DefaultDuration": "once", 12 | "InterceptUnknown": false, 13 | "ProcMonitorMethod": "ebpf", 14 | "LogLevel": 2, 15 | "LogUTC": true, 16 | "LogMicro": false, 17 | "Firewall": "nftables", 18 | "FwOptions": { 19 | "ConfigPath": "/etc/opensnitchd/system-fw.json", 20 | "MonitorInterval": "15s", 21 | "QueueBypass": true 22 | }, 23 | "Rules": { 24 | "Path": "/etc/opensnitchd/rules/", 25 | "EnableChecksums": false 26 | }, 27 | "Ebpf": { 28 | "EventsWorkers": 8, 29 | "QueueEventsSize": 0 30 | }, 31 | "Stats": { 32 | "MaxEvents": 250, 33 | "MaxStats": 25, 34 | "Workers": 6 35 | }, 36 | "Internal": { 37 | "GCPercent": 100, 38 | "FlushConnsOnStart": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /daemon/dns/parse.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "github.com/evilsocket/opensnitch/daemon/netfilter" 5 | "github.com/google/gopacket/layers" 6 | ) 7 | 8 | // GetQuestions retrieves the domain names a process is trying to resolve. 9 | func GetQuestions(nfp *netfilter.Packet) (questions []string) { 10 | dnsLayer := nfp.Packet.Layer(layers.LayerTypeDNS) 11 | if dnsLayer == nil { 12 | return questions 13 | } 14 | 15 | dns, _ := dnsLayer.(*layers.DNS) 16 | for _, dnsQuestion := range dns.Questions { 17 | questions = append(questions, string(dnsQuestion.Name)) 18 | } 19 | 20 | return questions 21 | } 22 | -------------------------------------------------------------------------------- /daemon/dns/track.go: -------------------------------------------------------------------------------- 1 | package dns 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | 7 | "github.com/evilsocket/opensnitch/daemon/log" 8 | 9 | "github.com/google/gopacket" 10 | "github.com/google/gopacket/layers" 11 | ) 12 | 13 | var ( 14 | responses = make(map[string]string, 0) 15 | lock = sync.RWMutex{} 16 | ) 17 | 18 | // TrackAnswers obtains the resolved domains of a DNS query. 19 | // If the packet is UDP DNS, the domain names are added to the list of resolved domains. 20 | func TrackAnswers(packet gopacket.Packet) bool { 21 | udpLayer := packet.Layer(layers.LayerTypeUDP) 22 | if udpLayer == nil { 23 | return false 24 | } 25 | 26 | udp, ok := udpLayer.(*layers.UDP) 27 | if ok == false || udp == nil { 28 | return false 29 | } 30 | if udp.SrcPort != 53 { 31 | return false 32 | } 33 | 34 | dnsLayer := packet.Layer(layers.LayerTypeDNS) 35 | if dnsLayer == nil { 36 | return false 37 | } 38 | 39 | dnsAns, ok := dnsLayer.(*layers.DNS) 40 | if ok == false || dnsAns == nil { 41 | return false 42 | } 43 | 44 | for _, ans := range dnsAns.Answers { 45 | if ans.Name != nil { 46 | if ans.IP != nil { 47 | Track(ans.IP.String(), string(ans.Name)) 48 | } else if ans.CNAME != nil { 49 | Track(string(ans.CNAME), string(ans.Name)) 50 | } 51 | } 52 | } 53 | 54 | return true 55 | } 56 | 57 | // Track adds a resolved domain to the list. 58 | func Track(resolved string, hostname string) { 59 | lock.Lock() 60 | defer lock.Unlock() 61 | 62 | if len(resolved) > 3 && resolved[0:4] == "127." { 63 | return 64 | } 65 | if resolved == "::1" || resolved == hostname { 66 | return 67 | } 68 | responses[resolved] = hostname 69 | 70 | log.Debug("New DNS record: %s -> %s", resolved, hostname) 71 | } 72 | 73 | // Host returns if a resolved domain is in the list. 74 | func Host(resolved string) (host string, found bool) { 75 | lock.RLock() 76 | defer lock.RUnlock() 77 | 78 | host, found = responses[resolved] 79 | return 80 | } 81 | 82 | // HostOr checks if an IP has a domain name already resolved. 83 | // If the domain is in the list it's returned, otherwise the IP will be returned. 84 | func HostOr(ip net.IP, or string) string { 85 | if host, found := Host(ip.String()); found == true { 86 | // host might have been CNAME; go back until we reach the "root" 87 | seen := make(map[string]bool) // prevent possibility of loops 88 | for { 89 | orig, had := Host(host) 90 | if seen[orig] { 91 | break 92 | } 93 | if !had { 94 | break 95 | } 96 | seen[orig] = true 97 | host = orig 98 | } 99 | return host 100 | } 101 | return or 102 | } 103 | -------------------------------------------------------------------------------- /daemon/firewall/config/config_test.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func preloadConfCallback() { 8 | } 9 | 10 | func reloadConfCallback() { 11 | } 12 | 13 | func TestNftLoadFromDisk(t *testing.T) { 14 | /*skipIfNotPrivileged(t) 15 | 16 | conn, newNS = OpenSystemConn(t) 17 | defer CleanupSystemConn(t, newNS) 18 | nft.conn = conn 19 | */ 20 | cfg := &Config{} 21 | cfg.NewSystemFwConfig("", preloadConfCallback, reloadConfCallback) 22 | cfg.SetConfigFile("../nftables/testdata/test-sysfw-conf.json") 23 | if err := cfg.LoadDiskConfiguration(false); err != nil { 24 | t.Errorf("Error loading config from disk: %s", err) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /daemon/firewall/iptables/monitor.go: -------------------------------------------------------------------------------- 1 | package iptables 2 | 3 | import ( 4 | "github.com/evilsocket/opensnitch/daemon/core" 5 | "github.com/evilsocket/opensnitch/daemon/firewall/common" 6 | "github.com/evilsocket/opensnitch/daemon/log" 7 | ) 8 | 9 | // AreRulesLoaded checks if the firewall rules for intercept traffic are loaded. 10 | func (ipt *Iptables) AreRulesLoaded() bool { 11 | var outMangle6 string 12 | 13 | outMangle, err := core.Exec("iptables", []string{"-n", "-L", "OUTPUT", "-t", "mangle"}) 14 | if err != nil { 15 | return false 16 | } 17 | 18 | if core.IPv6Enabled { 19 | outMangle6, err = core.Exec("ip6tables", []string{"-n", "-L", "OUTPUT", "-t", "mangle"}) 20 | if err != nil { 21 | return false 22 | } 23 | } 24 | 25 | systemRulesLoaded := true 26 | ipt.chains.RLock() 27 | if len(ipt.chains.Rules) > 0 { 28 | for _, rule := range ipt.chains.Rules { 29 | if chainOut4, err4 := core.Exec("iptables", []string{"-n", "-L", rule.Chain, "-t", rule.Table}); err4 == nil { 30 | if ipt.regexSystemRulesQuery.FindString(chainOut4) == "" { 31 | systemRulesLoaded = false 32 | break 33 | } 34 | } 35 | if core.IPv6Enabled { 36 | if chainOut6, err6 := core.Exec("ip6tables", []string{"-n", "-L", rule.Chain, "-t", rule.Table}); err6 == nil { 37 | if ipt.regexSystemRulesQuery.FindString(chainOut6) == "" { 38 | systemRulesLoaded = false 39 | break 40 | } 41 | } 42 | } 43 | } 44 | } 45 | ipt.chains.RUnlock() 46 | 47 | result := ipt.regexRulesQuery.FindString(outMangle) != "" && 48 | systemRulesLoaded 49 | 50 | if core.IPv6Enabled { 51 | result = result && ipt.regexRulesQuery.FindString(outMangle6) != "" 52 | } 53 | 54 | return result 55 | } 56 | 57 | // reloadRulesCallback gets called when the interception rules are not present or after the configuration file changes. 58 | func (ipt *Iptables) reloadRulesCallback() { 59 | log.Important("firewall rules changed, reloading") 60 | ipt.CleanRules(false) 61 | ipt.AddSystemRules(common.ReloadRules, common.BackupChains) 62 | ipt.EnableInterception() 63 | } 64 | 65 | // preloadConfCallback gets called before the fw configuration is reloaded 66 | func (ipt *Iptables) preloadConfCallback() { 67 | log.Info("iptables config changed, reloading") 68 | ipt.DeleteSystemRules(common.ForcedDelRules, common.BackupChains, log.GetLogLevel() == log.DEBUG) 69 | } 70 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/chains_test.go: -------------------------------------------------------------------------------- 1 | package nftables_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs" 7 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest" 8 | "github.com/google/nftables" 9 | ) 10 | 11 | func TestChains(t *testing.T) { 12 | nftest.SkipIfNotPrivileged(t) 13 | 14 | conn, newNS := nftest.OpenSystemConn(t) 15 | defer nftest.CleanupSystemConn(t, newNS) 16 | nftest.Fw.Conn = conn 17 | 18 | if nftest.Fw.AddInterceptionTables() != nil { 19 | t.Error("Error adding interception tables") 20 | } 21 | 22 | t.Run("AddChain", func(t *testing.T) { 23 | filterPolicy := nftables.ChainPolicyAccept 24 | chn := nftest.Fw.AddChain( 25 | exprs.NFT_HOOK_INPUT, 26 | exprs.NFT_CHAIN_FILTER, 27 | exprs.NFT_FAMILY_INET, 28 | nftables.ChainPriorityFilter, 29 | nftables.ChainTypeFilter, 30 | nftables.ChainHookInput, 31 | filterPolicy) 32 | if chn == nil { 33 | t.Error("chain input-filter-inet not created") 34 | } 35 | if !nftest.Fw.Commit() { 36 | t.Error("error adding input-filter-inet chain") 37 | } 38 | }) 39 | 40 | t.Run("getChain", func(t *testing.T) { 41 | tblfilter := nftest.Fw.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET) 42 | if tblfilter == nil { 43 | t.Error("table filter-inet not created") 44 | } 45 | 46 | chn := nftest.Fw.GetChain(exprs.NFT_HOOK_INPUT, tblfilter, exprs.NFT_FAMILY_INET) 47 | if chn == nil { 48 | t.Error("chain input-filter-inet not added") 49 | } 50 | }) 51 | 52 | t.Run("delChain", func(t *testing.T) { 53 | tblfilter := nftest.Fw.GetTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET) 54 | if tblfilter == nil { 55 | t.Error("table filter-inet not created") 56 | } 57 | 58 | chn := nftest.Fw.GetChain(exprs.NFT_HOOK_INPUT, tblfilter, exprs.NFT_FAMILY_INET) 59 | if chn == nil { 60 | t.Error("chain input-filter-inet not added") 61 | } 62 | 63 | if err := nftest.Fw.DelChain(chn); err != nil { 64 | t.Error("error deleting chain input-filter-inet") 65 | } 66 | }) 67 | 68 | nftest.Fw.DelSystemTables() 69 | } 70 | 71 | // TestAddInterceptionChains checks if the needed tables and chains have been created. 72 | // We use 2: output-mangle-inet for intercepting outbound connections, and input-filter-inet for DNS responses interception 73 | /*func TestAddInterceptionChains(t *testing.T) { 74 | nftest.SkipIfNotPrivileged(t) 75 | 76 | if err := nftest.Fw.AddInterceptionTables(); err != nil { 77 | t.Errorf("Error adding interception tables: %s", err) 78 | } 79 | 80 | if err := nftest.Fw.AddInterceptionChains(); err != nil { 81 | t.Errorf("Error adding interception chains: %s", err) 82 | } 83 | 84 | nftest.Fw.DelSystemTables() 85 | }*/ 86 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/counter.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "github.com/google/nftables/expr" 5 | ) 6 | 7 | // NewExprCounter returns a counter for packets or bytes. 8 | func NewExprCounter(counterName string) *[]expr.Any { 9 | return &[]expr.Any{ 10 | &expr.Objref{ 11 | Type: 1, 12 | Name: counterName, 13 | }, 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/counter_test.go: -------------------------------------------------------------------------------- 1 | package exprs_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs" 7 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest" 8 | "github.com/google/nftables" 9 | ) 10 | 11 | func TestExprNamedCounter(t *testing.T) { 12 | nftest.SkipIfNotPrivileged(t) 13 | 14 | conn, newNS := nftest.OpenSystemConn(t) 15 | defer nftest.CleanupSystemConn(t, newNS) 16 | nftest.Fw.Conn = conn 17 | 18 | // we must create the table before the counter object. 19 | tbl, _ := nftest.Fw.AddTable("yyy", exprs.NFT_FAMILY_INET) 20 | 21 | nftest.Fw.Conn.AddObj( 22 | &nftables.CounterObj{ 23 | Table: &nftables.Table{ 24 | Name: "yyy", 25 | Family: nftables.TableFamilyINet, 26 | }, 27 | Name: "xxx-counter", 28 | Bytes: 0, 29 | Packets: 0, 30 | }, 31 | ) 32 | 33 | r, _ := nftest.AddTestRule(t, conn, exprs.NewExprCounter("xxx-counter")) 34 | if r == nil { 35 | t.Error("Error adding counter rule") 36 | return 37 | } 38 | 39 | objs, err := nftest.Fw.Conn.GetObjects(tbl) 40 | if err != nil { 41 | t.Errorf("Error retrieving objects from table %s: %s", tbl.Name, err) 42 | } 43 | if len(objs) != 1 { 44 | t.Errorf("%d objects found, expected 1", len(objs)) 45 | } 46 | counter, ok := objs[0].(*nftables.CounterObj) 47 | if !ok { 48 | t.Errorf("returned Obj is not CounterObj: %+v", objs[0]) 49 | } 50 | if counter.Name != "xxx-counter" { 51 | t.Errorf("CounterObj name differs: %s, expected 'xxx-counter'", counter.Name) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/ether.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/evilsocket/opensnitch/daemon/firewall/config" 9 | "github.com/google/nftables/expr" 10 | ) 11 | 12 | // NewExprEther creates a new expression to match ethernet MAC addresses 13 | func NewExprEther(values []*config.ExprValues) (*[]expr.Any, error) { 14 | etherExpr := []expr.Any{} 15 | macDir := uint32(6) 16 | 17 | for _, eth := range values { 18 | if eth.Key == NFT_DADDR { 19 | macDir = uint32(0) 20 | } else { 21 | macDir = uint32(6) 22 | } 23 | macaddr, err := parseMACAddr(eth.Value) 24 | if err != nil { 25 | return nil, err 26 | } 27 | etherExpr = append(etherExpr, []expr.Any{ 28 | &expr.Meta{Key: expr.MetaKeyIIFTYPE, Register: 1}, 29 | &expr.Cmp{ 30 | Op: expr.CmpOpEq, 31 | Register: 1, 32 | Data: []byte{0x01, 0x00}, 33 | }, 34 | &expr.Payload{ 35 | DestRegister: 1, 36 | Base: expr.PayloadBaseLLHeader, 37 | Offset: macDir, 38 | Len: 6, 39 | }, 40 | &expr.Cmp{ 41 | Op: expr.CmpOpEq, 42 | Register: 1, 43 | Data: macaddr, 44 | }, 45 | }...) 46 | } 47 | return ðerExpr, nil 48 | } 49 | 50 | func parseMACAddr(macValue string) ([]byte, error) { 51 | mac := strings.Split(macValue, ":") 52 | macaddr := make([]byte, 0) 53 | if len(mac) != 6 { 54 | return nil, fmt.Errorf("Invalid MAC address: %s", macValue) 55 | } 56 | for i, m := range mac { 57 | mm, err := hex.DecodeString(m) 58 | if err != nil { 59 | return nil, fmt.Errorf("Invalid MAC byte: %c (%s)", mm[i], macValue) 60 | } 61 | macaddr = append(macaddr, mm[0]) 62 | } 63 | return macaddr, nil 64 | } 65 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/iface.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "github.com/google/nftables/expr" 5 | ) 6 | 7 | // NewExprIface returns a new network interface expression 8 | func NewExprIface(iface string, isOut bool, cmpOp expr.CmpOp) *[]expr.Any { 9 | keyDev := expr.MetaKeyIIFNAME 10 | if isOut { 11 | keyDev = expr.MetaKeyOIFNAME 12 | } 13 | return &[]expr.Any{ 14 | &expr.Meta{Key: keyDev, Register: 1}, 15 | &expr.Cmp{ 16 | Op: cmpOp, 17 | Register: 1, 18 | Data: ifname(iface), 19 | }, 20 | } 21 | } 22 | 23 | // https://github.com/google/nftables/blob/master/nftables_test.go#L81 24 | func ifname(n string) []byte { 25 | buf := make([]byte, 16) 26 | length := len(n) 27 | // allow wildcards 28 | if n[length-1:] == "*" { 29 | return []byte(n[:length-1]) 30 | } 31 | copy(buf, []byte(n+"\x00")) 32 | return buf 33 | } 34 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/iface_test.go: -------------------------------------------------------------------------------- 1 | package exprs_test 2 | 3 | import ( 4 | "bytes" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs" 9 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/nftest" 10 | "github.com/google/nftables/expr" 11 | ) 12 | 13 | // https://github.com/evilsocket/opensnitch/blob/master/daemon/firewall/nftables/exprs/iface.go#L22 14 | func ifname(n string) []byte { 15 | buf := make([]byte, 16) 16 | length := len(n) 17 | // allow wildcards 18 | if n[length-1:] == "*" { 19 | return []byte(n[:length-1]) 20 | } 21 | copy(buf, []byte(n+"\x00")) 22 | return buf 23 | } 24 | 25 | func TestExprIface(t *testing.T) { 26 | nftest.SkipIfNotPrivileged(t) 27 | 28 | conn, newNS := nftest.OpenSystemConn(t) 29 | defer nftest.CleanupSystemConn(t, newNS) 30 | nftest.Fw.Conn = conn 31 | 32 | type ifaceTestsT struct { 33 | name string 34 | iface string 35 | out bool 36 | } 37 | tests := []ifaceTestsT{ 38 | {"test-in-iface-xxx", "in-iface0", false}, 39 | {"test-out-iface-xxx", "out-iface0", true}, 40 | {"test-out-iface-xxx-wildcard", "out-iface*", true}, 41 | } 42 | 43 | for _, test := range tests { 44 | t.Run(test.name, func(t *testing.T) { 45 | ifaceExpr := exprs.NewExprIface(test.iface, test.out, expr.CmpOpEq) 46 | r, _ := nftest.AddTestRule(t, conn, ifaceExpr) 47 | if r == nil { 48 | t.Error("Error adding rule with iface expression") 49 | } 50 | if total := len(r.Exprs); total != 2 { 51 | t.Errorf("expected 2 expressions, got %d: %+v", total, r.Exprs) 52 | } 53 | e := r.Exprs[0] 54 | if reflect.TypeOf(e).String() != "*expr.Meta" { 55 | t.Errorf("first expression should be *expr.Meta, instead of: %s", reflect.TypeOf(e)) 56 | } 57 | lExpr, ok := e.(*expr.Meta) 58 | if !ok { 59 | t.Errorf("invalid iface meta expr: %T", e) 60 | } 61 | if test.out && lExpr.Key != expr.MetaKeyOIFNAME { 62 | t.Errorf("iface Key should be MetaKeyOIFNAME instead of: %+v", lExpr) 63 | } else if !test.out && lExpr.Key != expr.MetaKeyIIFNAME { 64 | t.Errorf("iface Key should be MetaKeyIIFNAME instead of: %+v", lExpr) 65 | } 66 | 67 | e = r.Exprs[1] 68 | if reflect.TypeOf(e).String() != "*expr.Cmp" { 69 | t.Errorf("second expression should be *expr.Cmp, instead of: %s", reflect.TypeOf(e)) 70 | } 71 | lCmp, ok := e.(*expr.Cmp) 72 | if !ok { 73 | t.Errorf("invalid iface cmp expr: %T", e) 74 | } 75 | if !bytes.Equal(lCmp.Data, ifname(test.iface)) { 76 | t.Errorf("iface Cmp does not match: %v, expected: %v", lCmp.Data, ifname(test.iface)) 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/limit.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/evilsocket/opensnitch/daemon/firewall/config" 8 | "github.com/google/nftables/expr" 9 | ) 10 | 11 | // NewExprLimit returns a new limit expression. 12 | // limit rate [over] 1/second 13 | // to express bytes units, we use: 10-mbytes instead of nft's 10 mbytes 14 | func NewExprLimit(statement *config.ExprStatement) (*[]expr.Any, error) { 15 | var err error 16 | exprLimit := &expr.Limit{ 17 | Type: expr.LimitTypePkts, 18 | Over: false, 19 | Unit: expr.LimitTimeSecond, 20 | } 21 | 22 | for _, values := range statement.Values { 23 | switch values.Key { 24 | 25 | case NFT_LIMIT_OVER: 26 | exprLimit.Over = true 27 | 28 | case NFT_LIMIT_UNITS: 29 | exprLimit.Rate, err = strconv.ParseUint(values.Value, 10, 64) 30 | if err != nil { 31 | return nil, fmt.Errorf("Invalid limit rate: %s", values.Value) 32 | } 33 | 34 | case NFT_LIMIT_BURST: 35 | limitBurst := 0 36 | limitBurst, err = strconv.Atoi(values.Value) 37 | if err != nil || limitBurst == 0 { 38 | return nil, fmt.Errorf("Invalid burst limit: %s, err: %s", values.Value, err) 39 | } 40 | exprLimit.Burst = uint32(limitBurst) 41 | 42 | case NFT_LIMIT_UNITS_RATE: 43 | // units rate must be placed AFTER the rate 44 | exprLimit.Type, exprLimit.Rate = getLimitRate(values.Value, exprLimit.Rate) 45 | 46 | case NFT_LIMIT_UNITS_TIME: 47 | exprLimit.Unit = getLimitUnits(values.Value) 48 | } 49 | } 50 | 51 | return &[]expr.Any{exprLimit}, nil 52 | } 53 | 54 | func getLimitUnits(units string) (limitUnits expr.LimitTime) { 55 | switch units { 56 | case NFT_LIMIT_UNIT_MINUTE: 57 | limitUnits = expr.LimitTimeMinute 58 | case NFT_LIMIT_UNIT_HOUR: 59 | limitUnits = expr.LimitTimeHour 60 | case NFT_LIMIT_UNIT_DAY: 61 | limitUnits = expr.LimitTimeDay 62 | default: 63 | limitUnits = expr.LimitTimeSecond 64 | } 65 | 66 | return limitUnits 67 | } 68 | 69 | func getLimitRate(units string, rate uint64) (limitType expr.LimitType, limitRate uint64) { 70 | switch units { 71 | case NFT_LIMIT_UNIT_KBYTES: 72 | limitRate = rate * 1024 73 | limitType = expr.LimitTypePktBytes 74 | case NFT_LIMIT_UNIT_MBYTES: 75 | limitRate = (rate * 1024) * 1024 76 | limitType = expr.LimitTypePktBytes 77 | default: 78 | limitType = expr.LimitTypePkts 79 | limitRate, _ = strconv.ParseUint(units, 10, 64) 80 | } 81 | 82 | return 83 | } 84 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/log.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/firewall/config" 7 | "github.com/evilsocket/opensnitch/daemon/log" 8 | "github.com/google/nftables/expr" 9 | "golang.org/x/sys/unix" 10 | ) 11 | 12 | // NewExprLog returns a new log expression. 13 | func NewExprLog(statement *config.ExprStatement) (*[]expr.Any, error) { 14 | prefix := "opensnitch" 15 | logExpr := expr.Log{ 16 | Key: 1 << unix.NFTA_LOG_PREFIX, 17 | Data: []byte(prefix), 18 | } 19 | 20 | for _, values := range statement.Values { 21 | switch values.Key { 22 | case NFT_LOG_PREFIX: 23 | if values.Value == "" { 24 | return nil, fmt.Errorf("Invalid log prefix, it's empty") 25 | } 26 | logExpr.Data = []byte(values.Value) 27 | case NFT_LOG_LEVEL: 28 | lvl, err := getLogLevel(values.Value) 29 | if err != nil { 30 | log.Warning("%s", err) 31 | return nil, err 32 | } 33 | logExpr.Key |= 1 << unix.NFTA_LOG_LEVEL 34 | logExpr.Level = lvl 35 | // TODO 36 | // https://github.com/google/nftables/blob/main/nftables_test.go#L623 37 | //case exprs.NFT_LOG_FLAGS: 38 | //case exprs.NFT_LOG_GROUP: 39 | //case exprs.NFT_LOG_QTHRESHOLD: 40 | } 41 | } 42 | 43 | return &[]expr.Any{ 44 | &logExpr, 45 | }, nil 46 | 47 | } 48 | 49 | func getLogLevel(what string) (expr.LogLevel, error) { 50 | switch what { 51 | // https://github.com/google/nftables/blob/main/expr/log.go#L28 52 | case NFT_LOG_LEVEL_EMERG: 53 | return expr.LogLevelEmerg, nil 54 | case NFT_LOG_LEVEL_ALERT: 55 | return expr.LogLevelAlert, nil 56 | case NFT_LOG_LEVEL_CRIT: 57 | return expr.LogLevelCrit, nil 58 | case NFT_LOG_LEVEL_ERR: 59 | return expr.LogLevelErr, nil 60 | case NFT_LOG_LEVEL_WARN: 61 | return expr.LogLevelWarning, nil 62 | case NFT_LOG_LEVEL_NOTICE: 63 | return expr.LogLevelNotice, nil 64 | case NFT_LOG_LEVEL_INFO: 65 | return expr.LogLevelInfo, nil 66 | case NFT_LOG_LEVEL_DEBUG: 67 | return expr.LogLevelDebug, nil 68 | case NFT_LOG_LEVEL_AUDIT: 69 | return expr.LogLevelAudit, nil 70 | } 71 | 72 | return 0, fmt.Errorf("Invalid log level: %s", what) 73 | } 74 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/notrack.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import "github.com/google/nftables/expr" 4 | 5 | // NewNoTrack adds a new expression not to track connections. 6 | func NewNoTrack() *[]expr.Any { 7 | return &[]expr.Any{ 8 | &expr.Notrack{}, 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/operator.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "github.com/google/nftables/expr" 5 | ) 6 | 7 | // NewOperator translates a string comparator operator to nftables operator 8 | func NewOperator(operator string) expr.CmpOp { 9 | switch operator { 10 | case "!=": 11 | return expr.CmpOpNeq 12 | case ">": 13 | return expr.CmpOpGt 14 | case ">=": 15 | return expr.CmpOpGte 16 | case "<": 17 | return expr.CmpOpLt 18 | case "<=": 19 | return expr.CmpOpLte 20 | } 21 | 22 | return expr.CmpOpEq 23 | } 24 | 25 | // NewExprOperator returns a new comparator operator 26 | func NewExprOperator(op expr.CmpOp) *[]expr.Any { 27 | return &[]expr.Any{ 28 | &expr.Cmp{ 29 | Register: 1, 30 | Op: op, 31 | }, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/port.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | "strings" 7 | 8 | "github.com/google/nftables" 9 | "github.com/google/nftables/binaryutil" 10 | "github.com/google/nftables/expr" 11 | ) 12 | 13 | // NewExprPort returns a new port expression with the given matching operator. 14 | func NewExprPort(port string, op *expr.CmpOp) (*[]expr.Any, error) { 15 | eport, err := strconv.Atoi(port) 16 | if err != nil { 17 | return nil, err 18 | } 19 | return &[]expr.Any{ 20 | &expr.Cmp{ 21 | Register: 1, 22 | Op: *op, 23 | Data: binaryutil.BigEndian.PutUint16(uint16(eport))}, 24 | }, nil 25 | } 26 | 27 | // NewExprPortRange returns a new port range expression. 28 | func NewExprPortRange(sport string, cmpOp *expr.CmpOp) (*[]expr.Any, error) { 29 | ports := strings.Split(sport, "-") 30 | iport, err := strconv.Atoi(ports[0]) 31 | if err != nil { 32 | return nil, err 33 | } 34 | eport, err := strconv.Atoi(ports[1]) 35 | if err != nil { 36 | return nil, err 37 | } 38 | return &[]expr.Any{ 39 | &expr.Range{ 40 | Op: *cmpOp, 41 | Register: 1, 42 | FromData: binaryutil.BigEndian.PutUint16(uint16(iport)), 43 | ToData: binaryutil.BigEndian.PutUint16(uint16(eport)), 44 | }, 45 | }, nil 46 | 47 | } 48 | 49 | // NewExprPortSet returns a new set of ports. 50 | func NewExprPortSet(portv string) *[]nftables.SetElement { 51 | setElements := []nftables.SetElement{} 52 | ports := strings.Split(portv, ",") 53 | for _, portv := range ports { 54 | portExpr := exprPortSubSet(portv) 55 | if portExpr != nil { 56 | setElements = append(setElements, *portExpr...) 57 | } 58 | } 59 | 60 | return &setElements 61 | } 62 | 63 | func exprPortSubSet(portv string) *[]nftables.SetElement { 64 | port, err := strconv.Atoi(portv) 65 | if err != nil { 66 | return nil 67 | } 68 | 69 | return &[]nftables.SetElement{ 70 | {Key: binaryutil.BigEndian.PutUint16(uint16(port))}, 71 | } 72 | 73 | } 74 | 75 | // NewExprPortDirection returns a new expression to match connections based on 76 | // the direction of the connection (source, dest) 77 | func NewExprPortDirection(direction string) (*expr.Payload, error) { 78 | switch direction { 79 | case NFT_DPORT: 80 | return &expr.Payload{ 81 | DestRegister: 1, 82 | Base: expr.PayloadBaseTransportHeader, 83 | Offset: 2, 84 | Len: 2, 85 | }, nil 86 | case NFT_SPORT: 87 | return &expr.Payload{ 88 | DestRegister: 1, 89 | Base: expr.PayloadBaseTransportHeader, 90 | Offset: 0, 91 | Len: 2, 92 | }, nil 93 | default: 94 | return nil, fmt.Errorf("Not valid protocol direction: %s", direction) 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/exprs/quota.go: -------------------------------------------------------------------------------- 1 | package exprs 2 | 3 | import ( 4 | "fmt" 5 | "strconv" 6 | 7 | "github.com/evilsocket/opensnitch/daemon/firewall/config" 8 | "github.com/google/nftables/expr" 9 | ) 10 | 11 | // NewQuota returns a new quota expression. 12 | // TODO: named quotas 13 | func NewQuota(opts []*config.ExprValues) (*[]expr.Any, error) { 14 | over := false 15 | bytes := int64(0) 16 | used := int64(0) 17 | for _, opt := range opts { 18 | switch opt.Key { 19 | case NFT_QUOTA_OVER: 20 | over = true 21 | case NFT_QUOTA_UNIT_BYTES: 22 | b, err := strconv.ParseInt(opt.Value, 10, 64) 23 | if err != nil { 24 | return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value) 25 | } 26 | bytes = b 27 | case NFT_QUOTA_USED: 28 | // TODO: support for other size units 29 | b, err := strconv.ParseInt(opt.Value, 10, 64) 30 | if err != nil { 31 | return nil, fmt.Errorf("invalid quota initial consumed bytes: %s", opt.Value) 32 | } 33 | used = b 34 | case NFT_QUOTA_UNIT_KB: 35 | b, err := strconv.ParseInt(opt.Value, 10, 64) 36 | if err != nil { 37 | return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value) 38 | } 39 | bytes = b * 1024 40 | case NFT_QUOTA_UNIT_MB: 41 | b, err := strconv.ParseInt(opt.Value, 10, 64) 42 | if err != nil { 43 | return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value) 44 | } 45 | bytes = (b * 1024) * 1024 46 | case NFT_QUOTA_UNIT_GB: 47 | b, err := strconv.ParseInt(opt.Value, 10, 64) 48 | if err != nil { 49 | return nil, fmt.Errorf("invalid quota bytes: %s", opt.Value) 50 | } 51 | bytes = ((b * 1024) * 1024) * 1024 52 | default: 53 | return nil, fmt.Errorf("invalid quota key: %s", opt.Key) 54 | } 55 | } 56 | if bytes == 0 { 57 | return nil, fmt.Errorf("quota bytes cannot be 0") 58 | } 59 | return &[]expr.Any{ 60 | &expr.Quota{ 61 | Bytes: uint64(bytes), 62 | Consumed: uint64(used), 63 | Over: over, 64 | }, 65 | }, nil 66 | } 67 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/monitor.go: -------------------------------------------------------------------------------- 1 | package nftables 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/firewall/common" 7 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs" 8 | "github.com/evilsocket/opensnitch/daemon/log" 9 | ) 10 | 11 | // AreRulesLoaded checks if the firewall rules for intercept traffic are loaded. 12 | func (n *Nft) AreRulesLoaded() bool { 13 | n.Lock() 14 | defer n.Unlock() 15 | 16 | nRules := 0 17 | chains, err := n.Conn.ListChains() 18 | if err != nil { 19 | log.Warning("[nftables] error listing nftables chains: %s", err) 20 | return false 21 | } 22 | 23 | for _, c := range chains { 24 | rules, err := n.Conn.GetRule(c.Table, c) 25 | if err != nil { 26 | log.Warning("[nftables] Error listing rules: %s", err) 27 | continue 28 | } 29 | for rdx, r := range rules { 30 | if string(r.UserData) == InterceptionRuleKey { 31 | if c.Table.Name == exprs.NFT_CHAIN_FILTER && c.Name == exprs.NFT_HOOK_INPUT && rdx != 0 { 32 | log.Warning("nftables DNS rule not in 1st position (%d)", rdx) 33 | return false 34 | } 35 | nRules++ 36 | if c.Table.Name == exprs.NFT_CHAIN_MANGLE && rdx < len(rules)-2 { 37 | log.Warning("nfables queue rule is not the latest of the list (%d/%d), reloading", rdx, len(rules)) 38 | return false 39 | } 40 | } 41 | } 42 | } 43 | // we expect to have exactly 3 rules (2 queue and 1 dns). If there're less or more, then we 44 | // need to reload them. 45 | if nRules != 3 { 46 | log.Warning("nfables filter rules not loaded: %d", nRules) 47 | return false 48 | } 49 | 50 | return true 51 | } 52 | 53 | // ReloadConfCallback gets called after the configuration changes. 54 | func (n *Nft) ReloadConfCallback() { 55 | log.Important("reloadConfCallback changed, reloading") 56 | n.DeleteSystemRules(!common.ForcedDelRules, !common.RestoreChains, log.GetLogLevel() == log.DEBUG) 57 | n.AddSystemRules(common.ReloadRules, !common.BackupChains) 58 | } 59 | 60 | // ReloadRulesCallback gets called when the interception rules are not present. 61 | func (n *Nft) ReloadRulesCallback() { 62 | log.Important("nftables firewall rules changed, reloading") 63 | n.DisableInterception(log.GetLogLevel() == log.DEBUG) 64 | time.Sleep(time.Millisecond * 500) 65 | n.EnableInterception() 66 | } 67 | 68 | // PreloadConfCallback gets called before the fw configuration is loaded 69 | func (n *Nft) PreloadConfCallback() { 70 | log.Info("nftables config changed, reloading") 71 | n.DeleteSystemRules(!common.ForcedDelRules, common.RestoreChains, log.GetLogLevel() == log.DEBUG) 72 | } 73 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/nftest/nftest.go: -------------------------------------------------------------------------------- 1 | package nftest 2 | 3 | import ( 4 | "os" 5 | "runtime" 6 | "testing" 7 | 8 | nftb "github.com/evilsocket/opensnitch/daemon/firewall/nftables" 9 | "github.com/google/nftables" 10 | "github.com/vishvananda/netns" 11 | ) 12 | 13 | var ( 14 | conn *nftables.Conn 15 | newNS netns.NsHandle 16 | 17 | // Fw represents the nftables Fw object. 18 | Fw, _ = nftb.Fw() 19 | ) 20 | 21 | func init() { 22 | nftb.InitMapsStore() 23 | } 24 | 25 | // SkipIfNotPrivileged will skip the test from where it's invoked, 26 | // to skip the test if we don't have root privileges. 27 | // This may occur when executing the tests on restricted environments, 28 | // such as containers, chroots, etc. 29 | func SkipIfNotPrivileged(t *testing.T) { 30 | if os.Getenv("PRIVILEGED_TESTS") == "" { 31 | 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.") 32 | } 33 | } 34 | 35 | // OpenSystemConn opens a new connection with the kernel in a new namespace. 36 | // https://github.com/google/nftables/blob/8f2d395e1089dea4966c483fbeae7e336917c095/internal/nftest/system_conn.go#L15 37 | func OpenSystemConn(t *testing.T) (*nftables.Conn, netns.NsHandle) { 38 | t.Helper() 39 | // We lock the goroutine into the current thread, as namespace operations 40 | // such as those invoked by `netns.New()` are thread-local. This is undone 41 | // in nftest.CleanupSystemConn(). 42 | runtime.LockOSThread() 43 | 44 | ns, err := netns.New() 45 | if err != nil { 46 | t.Fatalf("netns.New() failed: %v", err) 47 | } 48 | t.Log("OpenSystemConn() with NS:", ns) 49 | c, err := nftables.New(nftables.WithNetNSFd(int(ns))) 50 | if err != nil { 51 | t.Fatalf("nftables.New() failed: %v", err) 52 | } 53 | return c, ns 54 | } 55 | 56 | // CleanupSystemConn closes the given namespace. 57 | func CleanupSystemConn(t *testing.T, newNS netns.NsHandle) { 58 | defer runtime.UnlockOSThread() 59 | 60 | if err := newNS.Close(); err != nil { 61 | t.Fatalf("newNS.Close() failed: %v", err) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /daemon/firewall/nftables/tables.go: -------------------------------------------------------------------------------- 1 | package nftables 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/firewall/nftables/exprs" 7 | "github.com/evilsocket/opensnitch/daemon/log" 8 | "github.com/google/nftables" 9 | ) 10 | 11 | // AddTable adds a new table to nftables. 12 | func (n *Nft) AddTable(name, family string) (*nftables.Table, error) { 13 | famCode := GetFamilyCode(family) 14 | tbl := &nftables.Table{ 15 | Family: famCode, 16 | Name: name, 17 | } 18 | n.Conn.AddTable(tbl) 19 | 20 | if !n.Commit() { 21 | return nil, fmt.Errorf("%s error adding system firewall table: %s, family: %s (%d)", logTag, name, family, famCode) 22 | } 23 | key := getTableKey(name, family) 24 | sysTables.Add(key, tbl) 25 | return tbl, nil 26 | } 27 | 28 | // GetTable retrieves an already added table to the system. 29 | func (n *Nft) GetTable(name, family string) *nftables.Table { 30 | return sysTables.Get(getTableKey(name, family)) 31 | } 32 | 33 | func getTableKey(name string, family interface{}) string { 34 | return fmt.Sprint(name, "-", family) 35 | } 36 | 37 | // AddInterceptionTables adds the needed tables to intercept traffic. 38 | func (n *Nft) AddInterceptionTables() error { 39 | if _, err := n.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET); err != nil { 40 | return err 41 | } 42 | if _, err := n.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET); err != nil { 43 | return err 44 | } 45 | return nil 46 | } 47 | 48 | // Contrary to iptables, in nftables there're no predefined rules. 49 | // Convention is though to use the iptables names by default. 50 | // We need at least: mangle and filter tables, inet family (IPv4 and IPv6). 51 | func (n *Nft) addSystemTables() { 52 | n.AddTable(exprs.NFT_CHAIN_MANGLE, exprs.NFT_FAMILY_INET) 53 | n.AddTable(exprs.NFT_CHAIN_FILTER, exprs.NFT_FAMILY_INET) 54 | } 55 | 56 | // return the number of rules that we didn't add. 57 | func (n *Nft) nonSystemRules(tbl *nftables.Table) int { 58 | chains, err := n.Conn.ListChains() 59 | if err != nil { 60 | return -1 61 | } 62 | t := 0 63 | for _, c := range chains { 64 | if tbl.Name != c.Table.Name && tbl.Family != c.Table.Family { 65 | continue 66 | } 67 | rules, err := n.Conn.GetRule(c.Table, c) 68 | if err != nil { 69 | return -1 70 | } 71 | t += len(rules) 72 | } 73 | 74 | return t 75 | } 76 | 77 | // DelSystemTables deletes tables created from fw configuration. 78 | func (n *Nft) DelSystemTables() { 79 | for k, tbl := range sysTables.List() { 80 | if n.nonSystemRules(tbl) != 0 { 81 | continue 82 | } 83 | n.Conn.DelTable(tbl) 84 | if !n.Commit() { 85 | log.Warning("error deleting system table: %s", k) 86 | continue 87 | } 88 | sysTables.Del(k) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /daemon/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/evilsocket/opensnitch/daemon 2 | 3 | go 1.21 4 | 5 | //toolchain go1.23.1 6 | 7 | require ( 8 | github.com/cilium/ebpf v0.16.0 9 | github.com/fsnotify/fsnotify v1.4.7 10 | github.com/golang/protobuf v1.5.0 11 | github.com/google/gopacket v1.1.19 12 | github.com/google/nftables v0.2.0 13 | github.com/google/uuid v1.3.0 14 | github.com/varlink/go v0.4.0 15 | github.com/vishvananda/netlink v1.3.0 16 | github.com/vishvananda/netns v0.0.4 17 | golang.org/x/net v0.23.0 18 | golang.org/x/sys v0.20.0 19 | google.golang.org/grpc v1.32.0 20 | google.golang.org/protobuf v1.26.0 21 | ) 22 | 23 | require ( 24 | github.com/google/go-cmp v0.6.0 // indirect 25 | github.com/josharian/native v1.1.0 // indirect 26 | github.com/mdlayher/netlink v1.7.2 // indirect 27 | github.com/mdlayher/socket v0.5.0 // indirect 28 | golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 // indirect 29 | golang.org/x/sync v0.6.0 // indirect 30 | golang.org/x/text v0.14.0 // indirect 31 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect 32 | ) 33 | -------------------------------------------------------------------------------- /daemon/log/formats/csv.go: -------------------------------------------------------------------------------- 1 | package formats 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/ui/protocol" 7 | ) 8 | 9 | // CSV name of the output format, used in json configs 10 | const CSV = "csv" 11 | 12 | // Csv object 13 | type Csv struct { 14 | } 15 | 16 | // NewCSV returns a new CSV transformer object. 17 | func NewCSV() *Csv { 18 | return &Csv{} 19 | } 20 | 21 | // Transform takes input arguments and formats them to CSV. 22 | func (c *Csv) Transform(args ...interface{}) (out string) { 23 | p := args[0] 24 | values := p.([]interface{}) 25 | for _, val := range values { 26 | switch val.(type) { 27 | case *protocol.Connection: 28 | con := val.(*protocol.Connection) 29 | out = fmt.Sprint(out, 30 | con.SrcIp, ",", 31 | con.SrcPort, ",", 32 | con.DstIp, ",", 33 | con.DstHost, ",", 34 | con.DstPort, ",", 35 | con.Protocol, ",", 36 | con.ProcessId, ",", 37 | con.UserId, ",", 38 | //con.ProcessComm, ",", 39 | con.ProcessPath, ",", 40 | con.ProcessArgs, ",", 41 | con.ProcessCwd, ",", 42 | ) 43 | default: 44 | out = fmt.Sprint(out, val, ",") 45 | } 46 | } 47 | out = out[:len(out)-1] 48 | 49 | return 50 | } 51 | -------------------------------------------------------------------------------- /daemon/log/formats/formats.go: -------------------------------------------------------------------------------- 1 | package formats 2 | 3 | import ( 4 | "log/syslog" 5 | "os" 6 | "strconv" 7 | "strings" 8 | 9 | "github.com/evilsocket/opensnitch/daemon/core" 10 | "github.com/evilsocket/opensnitch/daemon/ui/protocol" 11 | ) 12 | 13 | // LoggerFormat is the common interface that every format must meet. 14 | // Transform expects an arbitrary number of arguments and types, and 15 | // it must transform them to a string. 16 | // Arguments can be of type Connection, string, int, etc. 17 | type LoggerFormat interface { 18 | Transform(...interface{}) string 19 | } 20 | 21 | var ( 22 | ourPid = "" 23 | syslogLevel = "" 24 | ) 25 | 26 | func init() { 27 | ourPid = strconv.FormatUint(uint64(os.Getpid()), 10) 28 | syslogLevel = strconv.FormatUint(uint64(syslog.LOG_NOTICE|syslog.LOG_DAEMON), 10) 29 | } 30 | 31 | // transform protocol.Connection to Structured Data format. 32 | func connToSD(out string, val interface{}) string { 33 | checksums := "" 34 | tree := "" 35 | con := val.(*protocol.Connection) 36 | 37 | for k, v := range con.ProcessChecksums { 38 | checksums = core.ConcatStrings(checksums, k, ":", v) 39 | } 40 | for _, y := range con.ProcessTree { 41 | tree = core.ConcatStrings(tree, y.Key, ",") 42 | } 43 | 44 | // TODO: allow to configure this via configuration file. 45 | return core.ConcatStrings(out, 46 | " SRC=\"", con.SrcIp, "\"", 47 | " SPT=\"", strconv.FormatUint(uint64(con.SrcPort), 10), "\"", 48 | " DST=\"", con.DstIp, "\"", 49 | " DSTHOST=\"", con.DstHost, "\"", 50 | " DPT=\"", strconv.FormatUint(uint64(con.DstPort), 10), "\"", 51 | " PROTO=\"", con.Protocol, "\"", 52 | " PID=\"", strconv.FormatUint(uint64(con.ProcessId), 10), "\"", 53 | " UID=\"", strconv.FormatUint(uint64(con.UserId), 10), "\"", 54 | //" COMM=", con.ProcessComm, "\"", 55 | " PATH=\"", con.ProcessPath, "\"", 56 | " CMDLINE=\"", strings.Join(con.ProcessArgs, " "), "\"", 57 | " CWD=\"", con.ProcessCwd, "\"", 58 | " CHECKSUMS=\"", checksums, "\"", 59 | " PROCTREE=\"", tree, "\"", 60 | // TODO: envs 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /daemon/log/formats/json.go: -------------------------------------------------------------------------------- 1 | package formats 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/core" 7 | "github.com/evilsocket/opensnitch/daemon/ui/protocol" 8 | ) 9 | 10 | // JSON name of the output format, used in our json config 11 | const JSON = "json" 12 | 13 | // events types 14 | const ( 15 | EvConnection = iota 16 | EvExec 17 | ) 18 | 19 | // JSONEventFormat object to be sent to the remote service. 20 | // TODO: Expand as needed: ebpf events, etc. 21 | type JSONEventFormat struct { 22 | Event interface{} `json:"Event"` 23 | Rule string `json:"Rule"` 24 | Action string `json:"Action"` 25 | Type uint8 `json:"Type"` 26 | } 27 | 28 | // NewJSON returns a new Json format, to send events as json. 29 | // The json is the protobuffer in json format. 30 | func NewJSON() *JSONEventFormat { 31 | return &JSONEventFormat{} 32 | } 33 | 34 | // Transform takes input arguments and formats them to JSON format. 35 | func (j *JSONEventFormat) Transform(args ...interface{}) (out string) { 36 | p := args[0] 37 | jObj := &JSONEventFormat{} 38 | 39 | values := p.([]interface{}) 40 | for n, val := range values { 41 | switch val.(type) { 42 | // TODO: 43 | // case *protocol.Rule: 44 | // case *protocol.Process: 45 | // case *protocol.Alerts: 46 | case *protocol.Connection: 47 | // XXX: All fields of the Connection object are sent, is this what we want? 48 | // or should we send an anonymous json? 49 | jObj.Event = val.(*protocol.Connection) 50 | jObj.Type = EvConnection 51 | 52 | case string: 53 | // action 54 | // rule name 55 | if n == 1 { 56 | jObj.Action = val.(string) 57 | } else if n == 2 { 58 | jObj.Rule = val.(string) 59 | } 60 | } 61 | } 62 | 63 | rawCfg, err := json.Marshal(&jObj) 64 | if err != nil { 65 | return 66 | } 67 | out = core.ConcatStrings(string(rawCfg), "\n\n") 68 | return 69 | } 70 | -------------------------------------------------------------------------------- /daemon/log/formats/rfc3164.go: -------------------------------------------------------------------------------- 1 | package formats 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/evilsocket/opensnitch/daemon/ui/protocol" 8 | ) 9 | 10 | // RFC3164 name of the output format, used in our json config 11 | const RFC3164 = "rfc3164" 12 | 13 | // Rfc3164 object 14 | type Rfc3164 struct { 15 | seq int 16 | } 17 | 18 | // NewRfc3164 returns a new Rfc3164 object, that transforms a message to 19 | // RFC3164 format. 20 | func NewRfc3164() *Rfc3164 { 21 | return &Rfc3164{} 22 | } 23 | 24 | // Transform takes input arguments and formats them to RFC3164 format. 25 | func (r *Rfc3164) Transform(args ...interface{}) (out string) { 26 | hostname := "" 27 | tag := "" 28 | arg1 := args[0] 29 | // we can do this better. Think. 30 | if len(args) > 1 { 31 | hostname = args[1].(string) 32 | tag = args[2].(string) 33 | } 34 | values := arg1.([]interface{}) 35 | for n, val := range values { 36 | switch val.(type) { 37 | case *protocol.Connection: 38 | out = connToSD(out, val) 39 | default: 40 | out = fmt.Sprint(out, " ARG", n, "=\"", val, "\"") 41 | } 42 | } 43 | out = fmt.Sprintf("<%s>%s %s %s[%s]: [%s]\n", 44 | syslogLevel, 45 | time.Now().Format(time.RFC3339), 46 | hostname, 47 | tag, 48 | ourPid, 49 | out[1:]) 50 | 51 | return 52 | } 53 | -------------------------------------------------------------------------------- /daemon/log/formats/rfc5424.go: -------------------------------------------------------------------------------- 1 | package formats 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/evilsocket/opensnitch/daemon/ui/protocol" 8 | ) 9 | 10 | // RFC5424 name of the output format, used in our json config 11 | const RFC5424 = "rfc5424" 12 | 13 | // Rfc5424 object 14 | type Rfc5424 struct { 15 | seq int 16 | } 17 | 18 | // NewRfc5424 returns a new Rfc5424 object, that transforms a message to 19 | // RFC5424 format (sort of). 20 | func NewRfc5424() *Rfc5424 { 21 | return &Rfc5424{} 22 | } 23 | 24 | // Transform takes input arguments and formats them to RFC5424 format. 25 | func (r *Rfc5424) Transform(args ...interface{}) (out string) { 26 | hostname := "" 27 | tag := "" 28 | event := "GENERIC" 29 | arg1 := args[0] 30 | if len(args) > 1 { 31 | arg2 := args[1] 32 | arg3 := args[2] 33 | hostname = arg2.(string) 34 | tag = arg3.(string) 35 | } 36 | values := arg1.([]interface{}) 37 | for n, val := range values { 38 | switch val.(type) { 39 | case *protocol.Connection: 40 | event = "CONNECTION" 41 | out = connToSD(out, val) 42 | default: 43 | out = fmt.Sprint(out, " ARG", n, "=\"", val, "\"") 44 | } 45 | } 46 | out = fmt.Sprintf("<%s>1 %s %s %s %s %s - [%s]\n", 47 | syslogLevel, 48 | time.Now().Format(time.RFC3339), 49 | hostname, 50 | tag, 51 | ourPid, 52 | event, 53 | out[1:]) 54 | 55 | return 56 | } 57 | -------------------------------------------------------------------------------- /daemon/log/loggers/remote_syslog.go: -------------------------------------------------------------------------------- 1 | package loggers 2 | 3 | import ( 4 | "github.com/evilsocket/opensnitch/daemon/log" 5 | ) 6 | 7 | const ( 8 | LOGGER_REMOTE_SYSLOG = "remote_syslog" 9 | ) 10 | 11 | type RemoteSyslog struct { 12 | *Remote 13 | } 14 | 15 | // NewRemoteSyslog returns a new object that manipulates and prints outbound connections 16 | // to a remote syslog server, with the given format (RFC5424 by default) 17 | func NewRemoteSyslog(cfg LoggerConfig) (*RemoteSyslog, error) { 18 | log.Info("NewRemoteSyslog logger: %v", cfg) 19 | 20 | r, err := NewRemote(cfg) 21 | r.mu.Lock() 22 | r.Name = LOGGER_REMOTE_SYSLOG 23 | r.mu.Unlock() 24 | rs := &RemoteSyslog{r} 25 | 26 | return rs, err 27 | } 28 | 29 | // https://cs.opensource.google/go/go/+/refs/tags/go1.18.2:src/log/syslog/syslog.go;l=286;drc=0a1a092c4b56a1d4033372fbd07924dad8cbb50b 30 | func (rs *RemoteSyslog) formatLine(msg string) string { 31 | return msg 32 | } 33 | -------------------------------------------------------------------------------- /daemon/log/loggers/syslog.go: -------------------------------------------------------------------------------- 1 | package loggers 2 | 3 | import ( 4 | "log/syslog" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/log" 7 | "github.com/evilsocket/opensnitch/daemon/log/formats" 8 | ) 9 | 10 | const ( 11 | LOGGER_SYSLOG = "syslog" 12 | ) 13 | 14 | // Syslog defines the logger that writes traces to the syslog. 15 | // It can write to the local or a remote daemon. 16 | type Syslog struct { 17 | Writer *syslog.Writer 18 | cfg LoggerConfig 19 | logFormat formats.LoggerFormat 20 | Name string 21 | Tag string 22 | } 23 | 24 | // NewSyslog returns a new object that manipulates and prints outbound connections 25 | // to syslog (local or remote), with the given format (RFC5424 by default) 26 | func NewSyslog(cfg LoggerConfig) (*Syslog, error) { 27 | var err error 28 | log.Info("NewSyslog logger: %v", cfg) 29 | 30 | sys := &Syslog{ 31 | Name: LOGGER_SYSLOG, 32 | cfg: cfg, 33 | } 34 | 35 | sys.logFormat = formats.NewRfc5424() 36 | if cfg.Format == formats.CSV { 37 | sys.logFormat = formats.NewCSV() 38 | } 39 | 40 | sys.Tag = logTag 41 | if cfg.Tag != "" { 42 | sys.Tag = cfg.Tag 43 | } 44 | 45 | if err = sys.Open(); err != nil { 46 | log.Error("Error loading logger: %s", err) 47 | return nil, err 48 | } 49 | log.Info("[%s logger] initialized: %v", sys.Name, cfg) 50 | 51 | return sys, err 52 | } 53 | 54 | // Open opens a new connection with a server or with the daemon. 55 | func (s *Syslog) Open() error { 56 | var err error 57 | s.Writer, err = syslog.New(syslog.LOG_NOTICE|syslog.LOG_DAEMON, logTag) 58 | 59 | return err 60 | } 61 | 62 | // Close closes the writer object 63 | func (s *Syslog) Close() error { 64 | return s.Writer.Close() 65 | } 66 | 67 | // Transform transforms data for proper ingestion. 68 | func (s *Syslog) Transform(args ...interface{}) (out string) { 69 | if s.logFormat != nil { 70 | out = s.logFormat.Transform(args...) 71 | } 72 | return 73 | } 74 | 75 | func (s *Syslog) Write(msg string) { 76 | if err := s.Writer.Notice(msg); err != nil { 77 | log.Error("[%s] write error: %s", s.Name, err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /daemon/netfilter/packet.go: -------------------------------------------------------------------------------- 1 | package netfilter 2 | 3 | import "C" 4 | 5 | import ( 6 | "github.com/google/gopacket" 7 | ) 8 | 9 | // packet consts 10 | const ( 11 | IPv4 = 4 12 | ) 13 | 14 | // Verdict holds the action to perform on a packet (NF_DROP, NF_ACCEPT, etc) 15 | type Verdict C.uint 16 | 17 | // VerdictContainer struct 18 | type VerdictContainer struct { 19 | Verdict Verdict 20 | Mark uint32 21 | Packet []byte 22 | } 23 | 24 | // Packet holds the data of a network packet 25 | type Packet struct { 26 | Packet gopacket.Packet 27 | Mark uint32 28 | verdictChannel chan VerdictContainer 29 | UID uint32 30 | NetworkProtocol uint8 31 | IfaceInIdx int 32 | IfaceOutIdx int 33 | } 34 | 35 | // SetVerdict emits a veredict on a packet 36 | func (p *Packet) SetVerdict(v Verdict) { 37 | p.verdictChannel <- VerdictContainer{Verdict: v, Packet: nil, Mark: 0} 38 | } 39 | 40 | // SetVerdictAndMark emits a veredict on a packet and marks it in order to not 41 | // analyze it again. 42 | func (p *Packet) SetVerdictAndMark(v Verdict, mark uint32) { 43 | p.verdictChannel <- VerdictContainer{Verdict: v, Packet: nil, Mark: mark} 44 | } 45 | 46 | // SetRequeueVerdict apply a verdict on a requeued packet 47 | func (p *Packet) SetRequeueVerdict(newQueueID uint16) { 48 | v := uint(NF_QUEUE) 49 | q := (uint(newQueueID) << 16) 50 | v = v | q 51 | p.verdictChannel <- VerdictContainer{Verdict: Verdict(v), Packet: nil, Mark: p.Mark} 52 | } 53 | 54 | // SetVerdictWithPacket apply a verdict, but with a new packet 55 | func (p *Packet) SetVerdictWithPacket(v Verdict, packet []byte) { 56 | p.verdictChannel <- VerdictContainer{Verdict: v, Packet: packet, Mark: 0} 57 | } 58 | 59 | // IsIPv4 returns if the packet is IPv4 60 | func (p *Packet) IsIPv4() bool { 61 | return p.NetworkProtocol == IPv4 62 | } 63 | -------------------------------------------------------------------------------- /daemon/netfilter/queue.c: -------------------------------------------------------------------------------- 1 | #include "queue.h" 2 | 3 | -------------------------------------------------------------------------------- /daemon/netlink/ifaces.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | "net" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/log" 7 | "github.com/vishvananda/netlink" 8 | ) 9 | 10 | // https://cs.opensource.google/go/go/+/refs/tags/go1.20.6:src/net/ip.go;l=133 11 | // TODO: remove when upgrading go version. 12 | func isPrivate(ip net.IP) bool { 13 | if ip4 := ip.To4(); ip4 != nil { 14 | return ip4[0] == 10 || 15 | (ip4[0] == 172 && ip4[1]&0xf0 == 16) || 16 | (ip4[0] == 192 && ip4[1] == 168) 17 | } 18 | return len(ip) == 16 && ip[0]&0xfe == 0xfc 19 | } 20 | 21 | // GetLocalAddrs returns the list of local IPs 22 | func GetLocalAddrs() map[string]netlink.Addr { 23 | localAddresses := make(map[string]netlink.Addr) 24 | addr, err := netlink.AddrList(nil, netlink.FAMILY_ALL) 25 | if err != nil { 26 | log.Error("eBPF error looking up this machine's addresses via netlink: %v", err) 27 | return nil 28 | } 29 | for _, a := range addr { 30 | log.Debug("local addr: %+v\n", a) 31 | localAddresses[a.IP.String()] = a 32 | } 33 | 34 | return localAddresses 35 | } 36 | 37 | // AddrUpdateToAddr translates AddrUpdate struct to Addr. 38 | func AddrUpdateToAddr(addr *netlink.AddrUpdate) netlink.Addr { 39 | return netlink.Addr{ 40 | IPNet: &addr.LinkAddress, 41 | LinkIndex: addr.LinkIndex, 42 | Flags: addr.Flags, 43 | Scope: addr.Scope, 44 | PreferedLft: addr.PreferedLft, 45 | ValidLft: addr.ValidLft, 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /daemon/netlink/socket_xdp.go: -------------------------------------------------------------------------------- 1 | package netlink 2 | 3 | import ( 4 | vnl "github.com/vishvananda/netlink" 5 | ) 6 | 7 | // SocketGetXDP dumps all the opened XDP sockets from kernel 8 | func SocketGetXDP() ([]*vnl.XDPDiagInfoResp, error) { 9 | // TODO: enable filtering 10 | return vnl.SocketDiagXDP() 11 | } 12 | -------------------------------------------------------------------------------- /daemon/netstat/entry.go: -------------------------------------------------------------------------------- 1 | package netstat 2 | 3 | import ( 4 | "net" 5 | ) 6 | 7 | // Entry holds the information of a /proc/net/* entry. 8 | // For example, /proc/net/tcp: 9 | // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode 10 | // 0: 0100007F:13AD 00000000:0000 0A 00000000:00000000 00:00000000 00000000 1000 0 18083222 11 | type Entry struct { 12 | Proto string 13 | SrcIP net.IP 14 | DstIP net.IP 15 | SrcPort uint 16 | DstPort uint 17 | Iface int 18 | UserId int 19 | INode int 20 | } 21 | 22 | // NewEntry creates a new entry with values from /proc/net/ 23 | func NewEntry(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint, userId int, iNode int) Entry { 24 | return Entry{ 25 | Proto: proto, 26 | SrcIP: srcIP, 27 | SrcPort: srcPort, 28 | DstIP: dstIP, 29 | DstPort: dstPort, 30 | UserId: userId, 31 | INode: iNode, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /daemon/netstat/find.go: -------------------------------------------------------------------------------- 1 | package netstat 2 | 3 | import ( 4 | "net" 5 | "strings" 6 | 7 | "github.com/evilsocket/opensnitch/daemon/core" 8 | "github.com/evilsocket/opensnitch/daemon/log" 9 | ) 10 | 11 | // FindEntry looks for the connection in the list of known connections in ProcFS. 12 | func FindEntry(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) *Entry { 13 | if entry := findEntryForProtocol(proto, srcIP, srcPort, dstIP, dstPort); entry != nil { 14 | return entry 15 | } 16 | 17 | ipv6Suffix := "6" 18 | if core.IPv6Enabled && strings.HasSuffix(proto, ipv6Suffix) == false { 19 | otherProto := proto + ipv6Suffix 20 | log.Debug("Searching for %s netstat entry instead of %s", otherProto, proto) 21 | if entry := findEntryForProtocol(otherProto, srcIP, srcPort, dstIP, dstPort); entry != nil { 22 | return entry 23 | } 24 | } 25 | 26 | return &Entry{ 27 | Proto: proto, 28 | SrcIP: srcIP, 29 | SrcPort: srcPort, 30 | DstIP: dstIP, 31 | DstPort: dstPort, 32 | UserId: -1, 33 | INode: -1, 34 | } 35 | } 36 | 37 | func findEntryForProtocol(proto string, srcIP net.IP, srcPort uint, dstIP net.IP, dstPort uint) *Entry { 38 | entries, err := Parse(proto) 39 | if err != nil { 40 | log.Warning("Error while searching for %s netstat entry: %s", proto, err) 41 | return nil 42 | } 43 | 44 | for _, entry := range entries { 45 | if srcIP.Equal(entry.SrcIP) && srcPort == entry.SrcPort && dstIP.Equal(entry.DstIP) && dstPort == entry.DstPort { 46 | return &entry 47 | } 48 | } 49 | 50 | return nil 51 | } 52 | -------------------------------------------------------------------------------- /daemon/netstat/parse_packet.go: -------------------------------------------------------------------------------- 1 | package netstat 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "regexp" 7 | 8 | "github.com/evilsocket/opensnitch/daemon/core" 9 | "github.com/evilsocket/opensnitch/daemon/log" 10 | ) 11 | 12 | var ( 13 | // sk RefCnt Type Proto Iface R Rmem User Inode 14 | // ffff90b72f893800 3 3 0003 3 1 0 0 257944535 15 | packetParser = regexp.MustCompile(`(?i)` + 16 | `[a-z0-9]+\s+` + // sk 17 | `[0-9]\s+` + // refCnt 18 | `([0-9])\s+` + // Type 19 | `([0-9a-z]+)\s+` + // proto 20 | `([0-9])\s+` + // iface 21 | `[0-9]\s+` + // r 22 | `[0-9]+\s+` + // rmem 23 | `([0-9]+)\s+` + // user 24 | `([0-9]+)`, // inode 25 | ) 26 | ) 27 | 28 | // ParsePacket scans and retrieves the opened sockets from /proc/net/packet 29 | func ParsePacket() ([]Entry, error) { 30 | filename := core.ConcatStrings("/proc/net/packet") 31 | fd, err := os.Open(filename) 32 | if err != nil { 33 | return nil, err 34 | } 35 | defer fd.Close() 36 | 37 | entries := make([]Entry, 0) 38 | scanner := bufio.NewScanner(fd) 39 | for lineno := 0; scanner.Scan(); lineno++ { 40 | // skip column names 41 | if lineno == 0 { 42 | continue 43 | } 44 | 45 | line := core.Trim(scanner.Text()) 46 | m := packetParser.FindStringSubmatch(line) 47 | if m == nil { 48 | log.Warning("Could not parse netstat line from %s: %s", filename, line) 49 | continue 50 | } 51 | // TODO: get proto, type, etc. 52 | en := Entry{} 53 | en.Iface = decToInt(m[3]) 54 | en.UserId = decToInt(m[4]) 55 | en.INode = decToInt(m[5]) 56 | 57 | entries = append(entries, en) 58 | } 59 | 60 | return entries, nil 61 | } 62 | -------------------------------------------------------------------------------- /daemon/network_aliases.json: -------------------------------------------------------------------------------- 1 | { 2 | "LAN": [ 3 | "10.0.0.0/8", 4 | "172.16.0.0/12", 5 | "192.168.0.0/16", 6 | "127.0.0.0/8", 7 | "::1", 8 | "fc00::/7" 9 | ], 10 | "MULTICAST": [ 11 | "224.0.0.0/4", 12 | "ff00::/8" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /daemon/opensnitchd-dinit: -------------------------------------------------------------------------------- 1 | # Application firewall OpenSnitch 2 | type = process 3 | command = /usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules 4 | restart = true 5 | smooth-recovery = yes 6 | restart-delay = 15 7 | stop-timeout = 10 8 | restart-limit-count = 0 9 | -------------------------------------------------------------------------------- /daemon/opensnitchd-openrc: -------------------------------------------------------------------------------- 1 | #!/sbin/openrc-run 2 | # OpenSnitch firewall service 3 | 4 | depend() { 5 | before net 6 | after iptables ip6tables 7 | use logger 8 | provide firewall 9 | } 10 | 11 | start_pre() { 12 | /bin/mkdir -p /etc/opensnitchd/rules 13 | /bin/chown -R root:root /etc/opensnitchd 14 | /bin/chown root:root /var/log/opensnitchd.log 15 | /bin/chmod -R 755 /etc/opensnitchd 16 | /bin/chmod -R 0644 /etc/opensnitchd/rules 17 | /bin/chmod 0600 /var/log/opensnitchd.log 18 | } 19 | 20 | start() { 21 | ebegin "Starting application firewall" 22 | # only if the verbose flag is not set (rc-service opensnitchd start -v) 23 | if [ -z "$VERBOSE" ]; then 24 | # redirect stdout and stderr to /dev/null 25 | /usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules -log-file /var/log/opensnitchd.log > /dev/null 2>&1 & 26 | else 27 | /usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules -log-file /var/log/opensnitchd.log 28 | fi 29 | eend $? 30 | } 31 | 32 | stop() { 33 | ebegin "Stopping application firewall" 34 | /usr/bin/pkill -SIGINT opensnitchd 35 | eend $? 36 | } 37 | -------------------------------------------------------------------------------- /daemon/opensnitchd.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Application firewall OpenSnitch 3 | Documentation=https://github.com/evilsocket/opensnitch/wiki 4 | 5 | [Service] 6 | Type=simple 7 | PermissionsStartOnly=true 8 | ExecStartPre=/bin/mkdir -p /etc/opensnitchd/rules 9 | ExecStart=/usr/local/bin/opensnitchd -rules-path /etc/opensnitchd/rules 10 | Restart=always 11 | RestartSec=30 12 | TimeoutStopSec=10 13 | 14 | [Install] 15 | WantedBy=multi-user.target 16 | -------------------------------------------------------------------------------- /daemon/procmon/activepids.go: -------------------------------------------------------------------------------- 1 | package procmon 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/log" 7 | "github.com/evilsocket/opensnitch/daemon/netlink/procmon" 8 | ) 9 | 10 | type value struct { 11 | Process *Process 12 | //Starttime uniquely identifies a process, it is the 22nd value in /proc/<PID>/stat 13 | //if another process starts with the same PID, it's Starttime will be unique 14 | Starttime uint64 15 | } 16 | 17 | var ( 18 | activePids = make(map[uint64]value) 19 | activePidsLock = sync.RWMutex{} 20 | ) 21 | 22 | // MonitorProcEvents listen for process events from kernel, via netlink. 23 | func MonitorProcEvents(stop <-chan struct{}) { 24 | log.Debug("MonitorProcEvents start") 25 | for { 26 | select { 27 | case <-stop: 28 | goto Exit 29 | case ev := <-procmon.ProcEventsChannel: 30 | if ev.IsExec() { 31 | // we don't receive the path of the process, therefore we need to discover it, 32 | // to check if the PID has replaced the PPID. 33 | proc := NewProcessWithParent(int(ev.PID), int(ev.TGID), "") 34 | proc.GetParent() 35 | proc.BuildTree() 36 | 37 | log.Debug("[procmon exec event] %d, pid:%d tgid:%d %s, %s -> %s\n", ev.TimeStamp, ev.PID, ev.TGID, proc.Comm, proc.Path, proc.Parent.Path) 38 | if item, needsUpdate, found := EventsCache.IsInStore(int(ev.PID), proc); found { 39 | if needsUpdate { 40 | EventsCache.Update(&item.Proc, proc) 41 | } 42 | log.Debug("[procmon exec event inCache] %d, pid:%d tgid:%d\n", ev.TimeStamp, ev.PID, ev.TGID) 43 | continue 44 | } 45 | EventsCache.Add(proc) 46 | } else if ev.IsExit() { 47 | p, _, found := EventsCache.IsInStore(int(ev.PID), nil) 48 | if found && p.Proc.IsAlive() == false { 49 | EventsCache.Delete(p.Proc.ID) 50 | } 51 | } 52 | } 53 | } 54 | Exit: 55 | log.Debug("MonitorProcEvents stopped") 56 | } 57 | -------------------------------------------------------------------------------- /daemon/procmon/ebpf/config.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import "github.com/evilsocket/opensnitch/daemon/log" 4 | 5 | // Config holds the configuration to customize ebpf module behaviour. 6 | type Config struct { 7 | ModulesPath string `json:"ModulesPath"` 8 | 9 | // system default value is 8, but it's not enough to handle "high" loads such 10 | // http downloads, torrent traffic, etc. (just regular desktop usage) 11 | // We set it to 64 by default (* PAGE_SIZE, which is usually 4a). 12 | RingBuffSize int `json:"RingBuffSize"` 13 | 14 | // number of workers to handle events from kernel 15 | EventsWorkers int `json:"EventsWorkers"` 16 | 17 | // max number of events in the queue received from the kernel. 18 | // 0 - Default behaviour. Each goroutine will wait for incoming messages, to 19 | // dispatch them one at a time. 20 | // > 0 - same as above, but if the daemon is not fast enough to dispatch the 21 | // events, they'll be queued. Once the daemon queue is full, kernel ebpf program 22 | // will have to wait/discard new events. (XXX: citation/testing needed). 23 | QueueEventsSize int `json:"QueueEventsSize"` 24 | } 25 | 26 | func setConfig(ebpfOpts Config) { 27 | ebpfCfg = ebpfOpts 28 | 29 | // ModulesPath defined in core.ebpf 30 | // QueueEventsSize defaults to 0 31 | 32 | if ebpfCfg.EventsWorkers == 0 { 33 | ebpfCfg.EventsWorkers = 8 34 | } 35 | 36 | if ebpfCfg.RingBuffSize == 0 { 37 | ebpfCfg.RingBuffSize = 64 38 | } 39 | 40 | log.Debug("[eBPF] config loaded: %v", ebpfCfg) 41 | } 42 | -------------------------------------------------------------------------------- /daemon/procmon/ebpf/debug.go: -------------------------------------------------------------------------------- 1 | package ebpf 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strconv" 7 | "syscall" 8 | 9 | daemonNetlink "github.com/evilsocket/opensnitch/daemon/netlink" 10 | ) 11 | 12 | // print map contents. used only for debugging 13 | 14 | //PrintEverything prints all the stats. used only for debugging 15 | func PrintEverything() { 16 | bash, _ := exec.LookPath("bash") 17 | //get the number of the first map 18 | out, err := exec.Command(bash, "-c", "bpftool map show | head -n 1 | cut -d ':' -f1").Output() 19 | if err != nil { 20 | fmt.Println("bpftool map dump name tcpMap ", err) 21 | } 22 | i, _ := strconv.Atoi(string(out[:len(out)-1])) 23 | fmt.Println("i is", i) 24 | 25 | //dump all maps for analysis 26 | for j := i; j < i+14; j++ { 27 | _, _ = exec.Command(bash, "-c", "bpftool map dump id "+strconv.Itoa(j)+" > dump"+strconv.Itoa(j)).Output() 28 | } 29 | 30 | alreadyEstablished.RLock() 31 | for sock1, v := range alreadyEstablished.TCP { 32 | fmt.Println(*sock1, v) 33 | } 34 | 35 | fmt.Println("---------------------") 36 | for sock1, v := range alreadyEstablished.TCPv6 { 37 | fmt.Println(*sock1, v) 38 | } 39 | alreadyEstablished.RUnlock() 40 | 41 | fmt.Println("---------------------") 42 | sockets, _ := daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_TCP) 43 | for idx := range sockets { 44 | fmt.Println("socket tcp: ", sockets[idx]) 45 | } 46 | fmt.Println("---------------------") 47 | sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_TCP) 48 | for idx := range sockets { 49 | fmt.Println("socket tcp6: ", sockets[idx]) 50 | } 51 | fmt.Println("---------------------") 52 | sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET, syscall.IPPROTO_UDP) 53 | for idx := range sockets { 54 | fmt.Println("socket udp: ", sockets[idx]) 55 | } 56 | fmt.Println("---------------------") 57 | sockets, _ = daemonNetlink.SocketsDump(syscall.AF_INET6, syscall.IPPROTO_UDP) 58 | for idx := range sockets { 59 | fmt.Println("socket udp6: ", sockets[idx]) 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /daemon/procmon/find_test.go: -------------------------------------------------------------------------------- 1 | package procmon 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestGetProcPids(t *testing.T) { 9 | pids := getProcPids("/proc") 10 | 11 | if len(pids) == 0 { 12 | t.Error("getProcPids() should not be 0", pids) 13 | } 14 | } 15 | 16 | func TestLookupPidDescriptors(t *testing.T) { 17 | pidsFd := lookupPidDescriptors(fmt.Sprint("/proc/", myPid, "/fd/"), myPid) 18 | if len(pidsFd) == 0 { 19 | t.Error("getProcPids() should not be 0", pidsFd) 20 | } 21 | } 22 | 23 | func TestLookupPidInProc(t *testing.T) { 24 | // we expect that the inode 1 points to /dev/null 25 | expect := "/dev/null" 26 | foundPid := lookupPidInProc("/proc/", expect, "", myPid) 27 | if foundPid == -1 { 28 | t.Error("lookupPidInProc() should not return -1") 29 | } 30 | } 31 | 32 | func BenchmarkGetProcs(b *testing.B) { 33 | for i := 0; i < b.N; i++ { 34 | getProcPids("/proc") 35 | } 36 | } 37 | 38 | func BenchmarkLookupPidDescriptors(b *testing.B) { 39 | for i := 0; i < b.N; i++ { 40 | lookupPidDescriptors(fmt.Sprint("/proc/", myPid, "/fd/"), myPid) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /daemon/procmon/testdata/proc-environ: -------------------------------------------------------------------------------- 1 | EMPTY=�TEST1=xxx=123�TEST2=xxx=123==456�SSH_AGENT_PID=4873�XDG_CURRENT_DESKTOP=i3�USER=opensnitch�GJS_DEBUG_OUTPUT=stderr�XDG_SESSION_PATH=/org/freedesktop/DisplayManager/Session0�HOME=/tmp�DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus�XDG_VTNR=7�TEST_ENV=seat0�XDG_SEAT=seat0�XDG_DATA_DIRS=/usr/share/gnome:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share�LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01; 2 | -------------------------------------------------------------------------------- /daemon/rule/operator_aliases.go: -------------------------------------------------------------------------------- 1 | package rule 2 | 3 | import ( 4 | "encoding/json" 5 | "net" 6 | "os" 7 | ) 8 | 9 | var NetworkAliases = make(map[string][]string) 10 | var AliasIPCache = make(map[string][]*net.IPNet) 11 | 12 | func LoadAliases(filename string) error { 13 | data, err := os.ReadFile(filename) 14 | if err != nil { 15 | return err 16 | } 17 | 18 | var aliases map[string][]string 19 | if err := json.Unmarshal(data, &aliases); err != nil { 20 | return err 21 | } 22 | 23 | for alias, networks := range aliases { 24 | var ipNets []*net.IPNet 25 | for _, network := range networks { 26 | _, ipNet, err := net.ParseCIDR(network) 27 | if err != nil { 28 | // fmt.Printf("Error parsing CIDR for %s: %v\n", network, err) 29 | continue 30 | } 31 | ipNets = append(ipNets, ipNet) 32 | } 33 | AliasIPCache[alias] = ipNets 34 | // fmt.Printf("Alias '%s' loaded with the following networks: %v\n", alias, networks) 35 | } 36 | 37 | // fmt.Println("Network aliases successfully loaded into the cache.") 38 | return nil 39 | } 40 | 41 | func GetAliasByIP(ip string) string { 42 | ipAddr := net.ParseIP(ip) 43 | for alias, ipNets := range AliasIPCache { 44 | for _, ipNet := range ipNets { 45 | if ipNet.Contains(ipAddr) { 46 | // fmt.Printf("Alias '%s' found for IP address: %s in network %s\n", alias, ip, ipNet.String()) 47 | return alias 48 | } 49 | } 50 | } 51 | // fmt.Printf("No alias found for IP: %s\n", ip) 52 | return "" 53 | } 54 | 55 | func (o *Operator) SerializeData() string { 56 | alias := GetAliasByIP(o.Data) 57 | if alias != "" { 58 | return alias 59 | } 60 | return o.Data 61 | } 62 | -------------------------------------------------------------------------------- /daemon/rule/testdata/000-allow-chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2020-12-13T18:06:52.209804547+01:00", 3 | "updated": "2020-12-13T18:06:52.209857713+01:00", 4 | "name": "000-allow-chrome", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "simple", 11 | "operand": "process.path", 12 | "sensitive": false, 13 | "data": "/opt/google/chrome/chrome", 14 | "list": [] 15 | } 16 | } -------------------------------------------------------------------------------- /daemon/rule/testdata/001-deny-chrome.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2020-12-13T17:54:49.067148304+01:00", 3 | "updated": "2020-12-13T17:54:49.067213602+01:00", 4 | "name": "001-deny-chrome", 5 | "enabled": true, 6 | "precedence": false, 7 | "action": "deny", 8 | "duration": "always", 9 | "operator": { 10 | "type": "simple", 11 | "operand": "process.path", 12 | "sensitive": false, 13 | "data": "/opt/google/chrome/chrome", 14 | "list": [] 15 | } 16 | } -------------------------------------------------------------------------------- /daemon/rule/testdata/invalid-regexp-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2020-12-13T18:06:52.209804547+01:00", 3 | "updated": "2020-12-13T18:06:52.209857713+01:00", 4 | "name": "invalid-regexp-list", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "list", 11 | "operand": "list", 12 | "sensitive": false, 13 | "data": "[{\"type\": \"regexp\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"^(/di(rmngr$\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]", 14 | "list": [ 15 | { 16 | "type": "regexp", 17 | "operand": "process.path", 18 | "sensitive": false, 19 | "data": "^(/di(rmngr)$", 20 | "list": null 21 | }, 22 | { 23 | "type": "simple", 24 | "operand": "dest.port", 25 | "sensitive": false, 26 | "data": "53", 27 | "list": null 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /daemon/rule/testdata/invalid-regexp.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2020-12-13T18:06:52.209804547+01:00", 3 | "updated": "2020-12-13T18:06:52.209857713+01:00", 4 | "name": "invalid-regexp", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "regexp", 11 | "operand": "process.path", 12 | "sensitive": false, 13 | "data": "/opt/((.*)google/chrome/chrome", 14 | "list": [] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /daemon/rule/testdata/lists/domains/domainlists.txt: -------------------------------------------------------------------------------- 1 | # this line must be ignored, 0.0.0.0 www.test.org 2 | 0.0.0.0 www.test.org 3 | 127.0.0.1 www.test.org 4 | 0.0.0.0 opensnitch.io 5 | -------------------------------------------------------------------------------- /daemon/rule/testdata/lists/ips/ips.txt: -------------------------------------------------------------------------------- 1 | # this line must be ignored, 0.0.0.0 www.test.org 2 | 3 | # empty lines are also ignored 4 | 1.1.1.1 5 | 185.53.178.14 6 | # duplicated entries should be ignored 7 | 1.1.1.1 8 | -------------------------------------------------------------------------------- /daemon/rule/testdata/lists/nets/nets.txt: -------------------------------------------------------------------------------- 1 | # this line must be ignored, 0.0.0.0 www.test.org 2 | 3 | # empty lines are also ignored 4 | 1.1.1.0/24 5 | 185.53.178.0/24 6 | # duplicated entries should be ignored 7 | 1.1.1.0/24 8 | 9 | -------------------------------------------------------------------------------- /daemon/rule/testdata/lists/regexp/domainsregexp.txt: -------------------------------------------------------------------------------- 1 | # this line must be ignored, 0.0.0.0 www.test.org 2 | www.test.org 3 | www.test.org 4 | opensnitch.io 5 | -------------------------------------------------------------------------------- /daemon/rule/testdata/live_reload/test-live-reload-delete.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2020-12-13T18:06:52.209804547+01:00", 3 | "updated": "2020-12-13T18:06:52.209857713+01:00", 4 | "name": "test-live-reload-delete", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "deny", 8 | "duration": "always", 9 | "operator": { 10 | "type": "simple", 11 | "operand": "process.path", 12 | "sensitive": false, 13 | "data": "/usr/bin/curl", 14 | "list": [] 15 | } 16 | } -------------------------------------------------------------------------------- /daemon/rule/testdata/live_reload/test-live-reload-remove.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2020-12-13T18:06:52.209804547+01:00", 3 | "updated": "2020-12-13T18:06:52.209857713+01:00", 4 | "name": "test-live-reload-remove", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "deny", 8 | "duration": "always", 9 | "operator": { 10 | "type": "simple", 11 | "operand": "process.path", 12 | "sensitive": false, 13 | "data": "/usr/bin/curl", 14 | "list": [] 15 | } 16 | } -------------------------------------------------------------------------------- /daemon/rule/testdata/rule-disabled-operator-list-expanded.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2023-10-03T18:06:52.209804547+01:00", 3 | "updated": "2023-10-03T18:06:52.209857713+01:00", 4 | "name": "rule-disabled-with-operators-list-expanded", 5 | "enabled": false, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "list", 11 | "operand": "list", 12 | "sensitive": false, 13 | "data": "", 14 | "list": [ 15 | { 16 | "type": "simple", 17 | "operand": "process.path", 18 | "sensitive": false, 19 | "data": "/usr/bin/telnet", 20 | "list": null 21 | }, 22 | { 23 | "type": "simple", 24 | "operand": "dest.port", 25 | "sensitive": false, 26 | "data": "53", 27 | "list": null 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /daemon/rule/testdata/rule-disabled-operator-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2023-10-03T18:06:52.209804547+01:00", 3 | "updated": "2023-10-03T18:06:52.209857713+01:00", 4 | "name": "rule-disabled-with-operators-list-as-json-string", 5 | "enabled": false, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "list", 11 | "operand": "list", 12 | "sensitive": false, 13 | "data": "[{\"type\": \"simple\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"/usr/bin/telnet\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]", 14 | "list": [ 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /daemon/rule/testdata/rule-operator-list-data-empty.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2023-10-03T18:06:52.209804547+01:00", 3 | "updated": "2023-10-03T18:06:52.209857713+01:00", 4 | "name": "rule-with-operator-list-data-empty", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "list", 11 | "operand": "list", 12 | "sensitive": false, 13 | "data": "", 14 | "list": [ 15 | { 16 | "type": "simple", 17 | "operand": "process.path", 18 | "sensitive": false, 19 | "data": "/usr/bin/telnet", 20 | "list": null 21 | }, 22 | { 23 | "type": "simple", 24 | "operand": "dest.port", 25 | "sensitive": false, 26 | "data": "53", 27 | "list": null 28 | } 29 | ] 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /daemon/rule/testdata/rule-operator-list.json: -------------------------------------------------------------------------------- 1 | { 2 | "created": "2023-10-03T18:06:52.209804547+01:00", 3 | "updated": "2023-10-03T18:06:52.209857713+01:00", 4 | "name": "rule-with-operator-list", 5 | "enabled": true, 6 | "precedence": true, 7 | "action": "allow", 8 | "duration": "always", 9 | "operator": { 10 | "type": "list", 11 | "operand": "list", 12 | "sensitive": false, 13 | "data": "[{\"type\": \"simple\", \"operand\": \"process.path\", \"sensitive\": false, \"data\": \"/usr/bin/telnet\"}, {\"type\": \"simple\", \"operand\": \"dest.port\", \"data\": \"53\", \"sensitive\": false}]", 14 | "list": [ 15 | { 16 | "type": "simple", 17 | "operand": "process.path", 18 | "sensitive": false, 19 | "data": "/usr/bin/telnet", 20 | "list": null 21 | }, 22 | { 23 | "type": "simple", 24 | "operand": "dest.port", 25 | "sensitive": false, 26 | "data": "53", 27 | "list": null 28 | } 29 | ] 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /daemon/statistics/event.go: -------------------------------------------------------------------------------- 1 | package statistics 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/evilsocket/opensnitch/daemon/conman" 7 | "github.com/evilsocket/opensnitch/daemon/rule" 8 | "github.com/evilsocket/opensnitch/daemon/ui/protocol" 9 | ) 10 | 11 | type Event struct { 12 | Time time.Time 13 | Connection *conman.Connection 14 | Rule *rule.Rule 15 | } 16 | 17 | func NewEvent(con *conman.Connection, match *rule.Rule) *Event { 18 | return &Event{ 19 | Time: time.Now(), 20 | Connection: con, 21 | Rule: match, 22 | } 23 | } 24 | 25 | func (e *Event) Serialize() *protocol.Event { 26 | return &protocol.Event{ 27 | Time: e.Time.Format("2006-01-02 15:04:05"), 28 | Connection: e.Connection.Serialize(), 29 | Rule: e.Rule.Serialize(), 30 | Unixnano: e.Time.UnixNano(), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /daemon/tasks/base.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "context" 5 | ) 6 | 7 | // TaskBase holds the common fields of every task. 8 | // Warning: don't define fields in tasks with these names. 9 | type TaskBase struct { 10 | Ctx context.Context 11 | Cancel context.CancelFunc 12 | Results chan interface{} 13 | Errors chan error 14 | 15 | // Stop the task if the daemon is disconnected from the GUI (server). 16 | // Some tasks don't need to run if the daemon is not connected to the GUI, 17 | // like PIDMonitor, SocketsMonitor,etc. 18 | // There might be other tasks that will perform some actions, and they 19 | // may send a notification on finish. 20 | StopOnDisconnect bool 21 | } 22 | 23 | // Task defines the interface for tasks that the task manager will execute. 24 | type Task interface { 25 | // Start starts the task, potentially running it asynchronously. 26 | Start(ctx context.Context, cancel context.CancelFunc) error 27 | 28 | // Stop stops the task. 29 | Stop() error 30 | 31 | Pause() error 32 | Resume() error 33 | 34 | // Results returns a channel that can be used to receive task results. 35 | Results() <-chan interface{} 36 | 37 | // channel used to send errors 38 | Errors() <-chan error 39 | } 40 | 41 | // TaskNotification is the data we receive when a new task is started from 42 | // the GUI (server). 43 | // The notification.data field will contain a string like: 44 | // '{"name": "...", "data": {"interval": "3s", "...": ...} }' 45 | // 46 | // where Name is the task to start, sa defined by the Name var of each task, 47 | // and Data is the configuration of each task (a map[string]string, converted by the json package). 48 | type TaskNotification struct { 49 | // Data of the task. 50 | Data interface{} 51 | 52 | // Name of the task. 53 | Name string 54 | } 55 | -------------------------------------------------------------------------------- /daemon/tasks/doc.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | // Copyright 2024 The OpenSnitch Authors. All rights reserved. 4 | // Use of this source code is governed by the GPLv3 5 | // license that can be found in the LICENSE file. 6 | 7 | /* 8 | Package tasks manages actions launched by/to the daemon. 9 | 10 | These tasks are handled by the TaskManager, which is in charge of start new 11 | task, update and stop them. 12 | 13 | The name of each task serves as the unique key inside the task manager. 14 | Some tasks will be unique, like SocketsMonitor, and others might have more than one instance, like "pid-monitor-123", "pid-monitor-987". 15 | 16 | Tasks run in background. 17 | 18 | Some tasks may run periodically (every 5s, every 2 days, ...), others will run until stop and others until a timeout. 19 | 20 | */ 21 | -------------------------------------------------------------------------------- /daemon/tasks/main_test.go: -------------------------------------------------------------------------------- 1 | package tasks 2 | 3 | import ( 4 | "context" 5 | "testing" 6 | ) 7 | 8 | type BasicTask struct { 9 | TaskBase 10 | Name string 11 | } 12 | 13 | func (pm *BasicTask) Start(ctx context.Context, cancel context.CancelFunc) error { 14 | return nil 15 | } 16 | func (pm *BasicTask) Pause() error { 17 | return nil 18 | } 19 | func (pm *BasicTask) Resume() error { 20 | return nil 21 | } 22 | func (pm *BasicTask) Stop() error { 23 | return nil 24 | } 25 | func (pm *BasicTask) Errors() <-chan error { 26 | return pm.TaskBase.Errors 27 | } 28 | func (pm *BasicTask) Results() <-chan interface{} { 29 | return pm.TaskBase.Results 30 | } 31 | 32 | var basicTask = BasicTask{ 33 | TaskBase: TaskBase{ 34 | Results: make(chan interface{}), 35 | Errors: make(chan error), 36 | }, 37 | Name: "basic-task", 38 | } 39 | 40 | func TestTaskManager(t *testing.T) { 41 | tkMgr := NewTaskManager() 42 | 43 | t.Run("AddTask", func(t *testing.T) { 44 | _, err := tkMgr.AddTask(basicTask.Name, &basicTask) 45 | if err != nil { 46 | t.Error("AddTask():", err) 47 | } 48 | }) 49 | 50 | t.Run("GetTask", func(t *testing.T) { 51 | if tk, found := tkMgr.GetTask(basicTask.Name); !found { 52 | t.Error("GetTask() not found:", tk) 53 | } 54 | }) 55 | 56 | t.Run("RemoveTask", func(t *testing.T) { 57 | if err := tkMgr.RemoveTask(basicTask.Name); err != nil { 58 | t.Error("RemoveTask() error:", err) 59 | } 60 | if tk, found := tkMgr.GetTask(basicTask.Name); found { 61 | t.Error("RemoveTask() task note removed:", tk) 62 | } 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /daemon/tasks/nodemonitor/main_test.go: -------------------------------------------------------------------------------- 1 | package nodemonitor 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "syscall" 7 | "testing" 8 | "time" 9 | 10 | "github.com/evilsocket/opensnitch/daemon/tasks" 11 | ) 12 | 13 | var tkMgr = tasks.NewTaskManager() 14 | 15 | func TestNodeMonitor(t *testing.T) { 16 | taskName, nodeMon := New("my-node", "1s", false) 17 | activity := false 18 | var ctx context.Context 19 | var err error 20 | var sysinfoRaw string 21 | 22 | t.Run("AddTask", func(t *testing.T) { 23 | ctx, err = tkMgr.AddTask(taskName, nodeMon) 24 | if err != nil { 25 | t.Error("TaskManager.AddTask() error:", err) 26 | } 27 | }) 28 | 29 | go func(ctx context.Context) { 30 | for { 31 | select { 32 | case <-ctx.Done(): 33 | goto Exit 34 | case err := <-nodeMon.Errors(): 35 | t.Error("Error via channel Errors():", err) 36 | case temp := <-nodeMon.Results(): 37 | var ok bool 38 | sysinfoRaw, ok = temp.(string) 39 | if !ok { 40 | t.Error("Error on Results() channel:", temp) 41 | goto Exit 42 | } 43 | activity = true 44 | } 45 | } 46 | Exit: 47 | }(ctx) 48 | time.Sleep(3 * time.Second) 49 | if !activity { 50 | t.Error("Error: no activity after 5s") 51 | } 52 | 53 | t.Run("Unmarshal response", func(t *testing.T) { 54 | var sysinfo syscall.Sysinfo_t 55 | err = json.Unmarshal([]byte(sysinfoRaw), &sysinfo) 56 | if err != nil { 57 | t.Error("Error unmarshaling response:", err) 58 | } 59 | }) 60 | 61 | t.Run("RemoveTask", func(t *testing.T) { 62 | err = tkMgr.RemoveTask(taskName) 63 | if err != nil { 64 | t.Error("RemoveTask() error:", err) 65 | } 66 | if tk, found := tkMgr.GetTask(taskName); found { 67 | t.Error("Task not removed:", tk) 68 | } 69 | }) 70 | 71 | activity = false 72 | time.Sleep(2 * time.Second) 73 | if activity { 74 | t.Error("Error: task active after being removed/stopped") 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /daemon/tasks/pidmonitor/main_test.go: -------------------------------------------------------------------------------- 1 | package pidmonitor 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "os" 7 | "testing" 8 | "time" 9 | 10 | "github.com/evilsocket/opensnitch/daemon/procmon" 11 | "github.com/evilsocket/opensnitch/daemon/tasks" 12 | ) 13 | 14 | var tkMgr = tasks.NewTaskManager() 15 | 16 | func TestPIDMonitor(t *testing.T) { 17 | ourPID := os.Getpid() 18 | taskName, pidMon := New(ourPID, "1s", false) 19 | activity := false 20 | var ctx context.Context 21 | var err error 22 | var procRaw string 23 | 24 | t.Run("AddTask", func(t *testing.T) { 25 | ctx, err = tkMgr.AddTask(taskName, pidMon) 26 | if err != nil { 27 | t.Error("TaskManager.AddTask() error:", err) 28 | } 29 | }) 30 | 31 | go func(ctx context.Context) { 32 | for { 33 | select { 34 | case <-ctx.Done(): 35 | goto Exit 36 | case err := <-pidMon.Errors(): 37 | t.Error("Error via channel Errors():", err) 38 | case temp := <-pidMon.Results(): 39 | var ok bool 40 | procRaw, ok = temp.(string) 41 | if !ok { 42 | t.Error("Error on Results() channel:", temp) 43 | goto Exit 44 | } 45 | activity = true 46 | } 47 | } 48 | Exit: 49 | }(ctx) 50 | time.Sleep(3 * time.Second) 51 | if !activity { 52 | t.Error("Error: no activity after 5s") 53 | } 54 | 55 | t.Run("Unmarshal response", func(t *testing.T) { 56 | var proc procmon.Process 57 | err = json.Unmarshal([]byte(procRaw), &proc) 58 | if err != nil { 59 | t.Error("Error unmarshaling response:", err) 60 | } 61 | if proc.ID != ourPID { 62 | t.Error("invalid Process object received:", ourPID, proc) 63 | } 64 | }) 65 | 66 | t.Run("RemoveTask", func(t *testing.T) { 67 | err = tkMgr.RemoveTask(taskName) 68 | if err != nil { 69 | t.Error("RemoveTask() error:", err) 70 | } 71 | if tk, found := tkMgr.GetTask(taskName); found { 72 | t.Error("Task not removed:", tk) 73 | } 74 | }) 75 | 76 | activity = false 77 | time.Sleep(2 * time.Second) 78 | if activity { 79 | t.Error("Task active after being removed/stopped") 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /daemon/tasks/socketsmonitor/options.go: -------------------------------------------------------------------------------- 1 | package socketsmonitor 2 | 3 | import ( 4 | "syscall" 5 | 6 | "golang.org/x/sys/unix" 7 | ) 8 | 9 | // Protos holds valid combinations of protocols, families and socket types that can be created. 10 | type Protos struct { 11 | Proto uint8 12 | Fam uint8 13 | } 14 | 15 | var options = []Protos{ 16 | {syscall.IPPROTO_DCCP, syscall.AF_INET}, 17 | {syscall.IPPROTO_DCCP, syscall.AF_INET6}, 18 | {syscall.IPPROTO_ICMPV6, syscall.AF_INET6}, 19 | {syscall.IPPROTO_ICMP, syscall.AF_INET}, 20 | {syscall.IPPROTO_IGMP, syscall.AF_INET}, 21 | {syscall.IPPROTO_IGMP, syscall.AF_INET6}, 22 | {syscall.IPPROTO_RAW, syscall.AF_INET}, 23 | {syscall.IPPROTO_RAW, syscall.AF_INET6}, 24 | {syscall.IPPROTO_SCTP, syscall.AF_INET}, 25 | {syscall.IPPROTO_SCTP, syscall.AF_INET6}, 26 | {syscall.IPPROTO_TCP, syscall.AF_INET}, 27 | {syscall.IPPROTO_TCP, syscall.AF_INET6}, 28 | {syscall.IPPROTO_UDP, syscall.AF_INET}, 29 | {syscall.IPPROTO_UDP, syscall.AF_INET6}, 30 | {syscall.IPPROTO_UDPLITE, syscall.AF_INET}, 31 | {syscall.IPPROTO_UDPLITE, syscall.AF_INET6}, 32 | 33 | // for AF_PACKET, Type is the "Protocol" (SOCK_DGRAM, SOCK_RAW) 34 | {syscall.IPPROTO_RAW, unix.AF_PACKET}, 35 | // here UDP is SOCK_DGRAM. Does not imply UDP protocol. 36 | {syscall.IPPROTO_UDP, unix.AF_PACKET}, 37 | //{syscall.IPPROTO_IP, unix.AF_PACKET}, 38 | //{unix.ETH_P_ALL, syscall.AF_PACKET}, 39 | } 40 | -------------------------------------------------------------------------------- /daemon/ui/protocol/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/daemon/ui/protocol/.gitkeep -------------------------------------------------------------------------------- /daemon/ui/testdata/default-config.json: -------------------------------------------------------------------------------- 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}} -------------------------------------------------------------------------------- /daemon/ui/testdata/default-config.json.orig: -------------------------------------------------------------------------------- 1 | { 2 | "Server": 3 | { 4 | "Address":"unix:///tmp/osui.sock", 5 | "LogFile":"/dev/stdout", 6 | "Authentication": { 7 | "Type": "tls-mutual", 8 | "TLSOptions": { 9 | "CACert": "/tmp/opensnitch/certs/unix-socket/ca-cert.pem", 10 | "ServerCert": "/tmp/opensnitch/certs/unix-socket/server-cert.pem", 11 | "ClientCert": "/tmp/opensnitch/certs/unix-socket/client-abstract-cert.pem", 12 | "ClientKey": "/tmp/opensnitch/certs/unix-socket/client-key.pem", 13 | "SkipVerify": false, 14 | "ClientAuthType": "req-and-verify-cert" 15 | } 16 | } 17 | }, 18 | "DefaultAction": "allow", 19 | "DefaultDuration": "once", 20 | "InterceptUnknown": false, 21 | "ProcMonitorMethod": "proc", 22 | "LogLevel": 0, 23 | "LogUTC": true, 24 | "LogMicro": false, 25 | "Firewall": "nftables", 26 | "FwOptions": { 27 | "ConfigPath": "/etc/opensnitchd/system-fw.json", 28 | "MonitorInterval": "25s", 29 | "QueueBypass": false 30 | }, 31 | "Rules": { 32 | "Path": "/etc/opensnitchd/rules", 33 | "EnableChecksums": true 34 | }, 35 | "Ebpf": { 36 | "ModulesPath": "/usr/lib/opensnitchd/ebpf" 37 | }, 38 | "Internal": { 39 | "GCPercent": 75 40 | }, 41 | "Stats": { 42 | "MaxEvents": 150, 43 | "MaxStats": 25, 44 | "Workers": 6 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ebpf_prog/Makefile: -------------------------------------------------------------------------------- 1 | # OpenSnitch - 2023 2 | # 3 | # On Debian based distros we need the following 2 directories. 4 | # Otherwise, just use the kernel headers from the kernel sources. 5 | # 6 | KERNEL_VER ?= $(shell ls -d /lib/modules/*/source | sort | tail -1 | cut -d/ -f4) 7 | KERNEL_DIR ?= /lib/modules/$(KERNEL_VER)/source 8 | KERNEL_HEADERS ?= /usr/src/linux-headers-$(KERNEL_VER)/ 9 | CC = clang 10 | LLC ?= llc 11 | ARCH ?= $(shell uname -m) 12 | 13 | # as in /usr/src/linux-headers-*/arch/ 14 | # TODO: extract correctly the archs, and add more if needed. 15 | ifeq ($(ARCH),x86_64) 16 | ARCH := x86 17 | else ifeq ($(ARCH),i686) 18 | ARCH := x86 19 | else ifeq ($(ARCH),armv7l) 20 | ARCH := arm 21 | else ifeq ($(ARCH),armv8l) 22 | ARCH := arm 23 | else ifeq ($(ARCH),aarch64) 24 | ARCH := arm64 25 | else ifeq ($(ARCH),loongarch64) 26 | ARCH := loongarch 27 | else ifeq ($(ARCH),riscv64) 28 | ARCH := riscv 29 | else ifeq ($(ARCH),s390x) 30 | ARCH := s390 31 | endif 32 | 33 | ifeq ($(ARCH),arm) 34 | # on previous archs, it fails with "SMP not supported on pre-ARMv6" 35 | EXTRA_FLAGS = "-D__LINUX_ARM_ARCH__=7" 36 | endif 37 | 38 | SRC := $(wildcard *.c) 39 | BIN := $(SRC:.c=.o) 40 | CFLAGS = -I. \ 41 | -I$(KERNEL_HEADERS)/arch/$(ARCH)/include/generated/ \ 42 | -I$(KERNEL_HEADERS)/include \ 43 | -include $(KERNEL_DIR)/include/linux/kconfig.h \ 44 | -I$(KERNEL_DIR)/include \ 45 | -I$(KERNEL_DIR)/include/uapi \ 46 | -I$(KERNEL_DIR)/include/generated/uapi \ 47 | -I$(KERNEL_DIR)/arch/$(ARCH)/include \ 48 | -I$(KERNEL_DIR)/arch/$(ARCH)/include/generated \ 49 | -I$(KERNEL_DIR)/arch/$(ARCH)/include/uapi \ 50 | -I$(KERNEL_DIR)/arch/$(ARCH)/include/generated/uapi \ 51 | -I$(KERNEL_DIR)/tools/testing/selftests/bpf/ \ 52 | -D__KERNEL__ -D__BPF_TRACING__ -Wno-unused-value -Wno-pointer-sign \ 53 | -D__TARGET_ARCH_$(ARCH) -Wno-compare-distinct-pointer-types \ 54 | $(EXTRA_FLAGS) \ 55 | -Wunused \ 56 | -Wno-unused-value \ 57 | -Wno-gnu-variable-sized-type-not-at-end \ 58 | -Wno-address-of-packed-member \ 59 | -Wno-tautological-compare \ 60 | -Wno-unknown-warning-option \ 61 | -fno-stack-protector \ 62 | -g -O2 -emit-llvm 63 | 64 | all: $(BIN) 65 | 66 | %.bc: %.c 67 | $(CC) $(CFLAGS) -c $< 68 | 69 | %.o: %.bc 70 | $(LLC) -march=bpf -mcpu=generic -filetype=obj -o $@ $< 71 | 72 | clean: 73 | rm -f $(BIN) 74 | 75 | .SUFFIXES: 76 | -------------------------------------------------------------------------------- /ebpf_prog/arm-clang-asm-fix.patch: -------------------------------------------------------------------------------- 1 | --- ../../arch/arm/include/asm/unified.h 2021-04-20 10:47:54.075834124 +0000 2 | +++ ../../arch/arm/include/asm/unified-clang-fix.h 2021-04-20 10:47:38.943811970 +0000 3 | @@ -11,7 +11,10 @@ 4 | #if defined(__ASSEMBLY__) 5 | .syntax unified 6 | #else 7 | -__asm__(".syntax unified"); 8 | +//__asm__(".syntax unified"); 9 | +#ifndef __clang__ 10 | + __asm__(".syntax unified"); 11 | +#endif 12 | #endif 13 | 14 | #ifdef CONFIG_CPU_V7M 15 | -------------------------------------------------------------------------------- /ebpf_prog/common.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENSNITCH_COMMON_H 2 | #define OPENSNITCH_COMMON_H 3 | 4 | #include "common_defs.h" 5 | 6 | //https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/limits.h#L13 7 | #ifndef MAX_PATH_LEN 8 | #define MAX_PATH_LEN 4096 9 | #endif 10 | 11 | //https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/binfmts.h#L16 12 | #define MAX_CMDLINE_LEN 4096 13 | // max args that I've been able to use before hitting the error: 14 | // "dereference of modified ctx ptr disallowed" 15 | #define MAX_ARGS 20 16 | #define MAX_ARG_SIZE 256 17 | 18 | // flags to indicate if we were able to read all the cmdline arguments, 19 | // or if one of the arguments is >= MAX_ARG_SIZE, or there more than MAX_ARGS 20 | #define COMPLETE_ARGS 0 21 | #define INCOMPLETE_ARGS 1 22 | 23 | #ifndef TASK_COMM_LEN 24 | #define TASK_COMM_LEN 16 25 | #endif 26 | 27 | #define BUF_SIZE_MAP_NS 256 28 | #define GLOBAL_MAP_NS "256" 29 | enum events_type { 30 | EVENT_NONE = 0, 31 | EVENT_EXEC, 32 | EVENT_EXECVEAT, 33 | EVENT_FORK, 34 | EVENT_SCHED_EXIT, 35 | }; 36 | 37 | struct trace_ev_common { 38 | short common_type; 39 | char common_flags; 40 | char common_preempt_count; 41 | int common_pid; 42 | }; 43 | 44 | struct trace_sys_enter_execve { 45 | struct trace_ev_common ext; 46 | 47 | int __syscall_nr; 48 | char *filename; 49 | const char *const *argv; 50 | const char *const *envp; 51 | }; 52 | 53 | struct trace_sys_enter_execveat { 54 | struct trace_ev_common ext; 55 | 56 | int __syscall_nr; 57 | char *filename; 58 | const char *const *argv; 59 | const char *const *envp; 60 | int flags; 61 | }; 62 | 63 | struct trace_sys_exit_execve { 64 | struct trace_ev_common ext; 65 | 66 | int __syscall_nr; 67 | long ret; 68 | }; 69 | 70 | struct data_t { 71 | u64 type; 72 | u32 pid; // PID as in the userspace term (i.e. task->tgid in kernel) 73 | u32 uid; 74 | // Parent PID as in the userspace term (i.e task->real_parent->tgid in kernel) 75 | u32 ppid; 76 | u32 ret_code; 77 | u16 _pad; 78 | u8 args_count; 79 | u8 args_partial; 80 | char filename[MAX_PATH_LEN]; 81 | char args[MAX_ARGS][MAX_ARG_SIZE]; 82 | char comm[TASK_COMM_LEN]; 83 | }; 84 | 85 | //----------------------------------------------------------------------------- 86 | // maps 87 | 88 | struct { 89 | __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); 90 | __type(key, u32); 91 | __type(value, struct data_t); 92 | __uint(max_entries, 1); 93 | } heapstore SEC(".maps"); 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /ebpf_prog/common_defs.h: -------------------------------------------------------------------------------- 1 | #ifndef OPENSNITCH_COMMON_DEFS_H 2 | #define OPENSNITCH_COMMON_DEFS_H 3 | 4 | #include <linux/sched.h> 5 | #include <linux/ptrace.h> 6 | #include <linux/byteorder/generic.h> 7 | #include <uapi/linux/bpf.h> 8 | #include "bpf_headers/bpf_helpers.h" 9 | #include "bpf_headers/bpf_tracing.h" 10 | //#include <bpf/bpf_core_read.h> 11 | 12 | #define BUF_SIZE_MAP_NS 256 13 | #define MAPSIZE 12000 14 | 15 | #ifndef htonll 16 | #define htonll(x) cpu_to_be64(x) 17 | #endif 18 | 19 | #define debug(fmt, ...) \ 20 | ( \ 21 | { \ 22 | char __fmt[] = fmt; \ 23 | bpf_trace_printk(__fmt, sizeof(__fmt), ##__VA_ARGS__); \ 24 | }) 25 | 26 | // even though we only need 32 bits of pid, on x86_32 ebpf verifier complained when pid type was set to u32 27 | typedef u64 pid_size_t; 28 | typedef u64 uid_size_t; 29 | 30 | enum bpf_pin_type { 31 | PIN_NONE = 0, 32 | PIN_OBJECT_NS, 33 | PIN_GLOBAL_NS, 34 | PIN_CUSTOM_NS, 35 | }; 36 | //----------------------------------- 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /proto/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /proto/Makefile: -------------------------------------------------------------------------------- 1 | all: ../daemon/ui/protocol/ui.pb.go ../ui/opensnitch/ui_pb2.py 2 | 3 | ../daemon/ui/protocol/ui.pb.go: ui.proto 4 | 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 5 | 6 | ../ui/opensnitch/ui_pb2.py: ui.proto 7 | python3 -m grpc_tools.protoc -I. --python_out=../ui/opensnitch/proto/ --grpc_python_out=../ui/opensnitch/proto/ ui.proto 8 | 9 | clean: 10 | @rm -rf ../daemon/ui/protocol/ui.pb.go 11 | @rm -rf ../daemon/ui/protocol/ui_grpc.pb.go 12 | @rm -rf ../ui/opensnitch/proto/ui_pb2.py 13 | @rm -rf ../ui/opensnitch/proto/ui_pb2_grpc.py 14 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # nothing to see here, just a utility i use to create new releases ^_^ 3 | 4 | CURRENT_VERSION=$(cat daemon/core/version.go | grep Version | cut -d '"' -f 2) 5 | TO_UPDATE=( 6 | daemon/core/version.go 7 | ui/version.py 8 | ) 9 | 10 | echo -n "Current version is $CURRENT_VERSION, select new version: " 11 | read NEW_VERSION 12 | echo "Creating version $NEW_VERSION ...\n" 13 | 14 | for file in "${TO_UPDATE[@]}" 15 | do 16 | echo "Patching $file ..." 17 | sed -i "s/$CURRENT_VERSION/$NEW_VERSION/g" $file 18 | git add $file 19 | done 20 | 21 | git commit -m "Releasing v$NEW_VERSION" 22 | git push 23 | 24 | git tag -a v$NEW_VERSION -m "Release v$NEW_VERSION" 25 | git push origin v$NEW_VERSION 26 | 27 | echo 28 | echo "All done, v$NEW_VERSION released ^_^" 29 | -------------------------------------------------------------------------------- /screenshots/opensnitch-ui-general-tab-deny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/screenshots/opensnitch-ui-general-tab-deny.png -------------------------------------------------------------------------------- /screenshots/opensnitch-ui-proc-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/screenshots/opensnitch-ui-proc-details.png -------------------------------------------------------------------------------- /screenshots/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/screenshots/screenshot.png -------------------------------------------------------------------------------- /ui/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build 3 | dist 4 | *.egg-info 5 | __pycache__ 6 | -------------------------------------------------------------------------------- /ui/LICENSE: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://github.com/gustavo-iniguez-goya/opensnitch 3 | Upstream-Name: python3-opensnitch-ui 4 | Files: * 5 | Copyright: 6 | 2017-2018 evilsocket 7 | 2019-2020 Gustavo Iñiguez Goia 8 | Comment: Debian packaging is licensed under the same terms as upstream 9 | License: GPL-3.0 10 | This program is free software; you can redistribute it 11 | and/or modify it under the terms of the GNU General Public 12 | License as published by the Free Software Foundation; either 13 | version 3 of the License, or (at your option) any later 14 | version. 15 | . 16 | This program is distributed in the hope that it will be 17 | useful, but WITHOUT ANY WARRANTY; without even the implied 18 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 19 | PURPOSE. See the GNU General Public License for more 20 | details. 21 | . 22 | You should have received a copy of the GNU General Public 23 | License along with this program. If not, If not, see 24 | http://www.gnu.org/licenses/. 25 | . 26 | On Debian systems, the full text of the GNU General Public 27 | License version 3 can be found in the file 28 | '/usr/share/common-licenses/GPL-3'. 29 | -------------------------------------------------------------------------------- /ui/MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include opensnitch/proto * 2 | recursive-include opensnitch/res * 3 | recursive-include opensnitch/i18n *.qm 4 | recursive-include opensnitch/database/migrations *.sql 5 | include LICENSE 6 | -------------------------------------------------------------------------------- /ui/Makefile: -------------------------------------------------------------------------------- 1 | all: opensnitch/resources_rc.py 2 | 3 | install: 4 | @pip3 install --upgrade . 5 | 6 | opensnitch/resources_rc.py: translations deps 7 | @pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc 8 | @find opensnitch/proto/ -name 'ui_pb2_grpc.py' -exec sed -i 's/^import ui_pb2/from . import ui_pb2/' {} \; 9 | 10 | translations: 11 | @cd i18n ; make 12 | 13 | deps: 14 | @pip3 install -r requirements.txt 15 | 16 | clean: 17 | @rm -rf *.pyc 18 | @rm -rf opensnitch/resources_rc.py 19 | -------------------------------------------------------------------------------- /ui/i18n/Makefile: -------------------------------------------------------------------------------- 1 | SOURCES += ../opensnitch/service.py \ 2 | ../opensnitch/dialogs/prompt/__init__.py \ 3 | ../opensnitch/dialogs/prompt/_utils.py \ 4 | ../opensnitch/dialogs/prompt/_details.py \ 5 | ../opensnitch/dialogs/prompt/_checksums.py \ 6 | ../opensnitch/dialogs/prompt/_constants.py \ 7 | ../opensnitch/dialogs/preferences.py \ 8 | ../opensnitch/dialogs/ruleseditor.py \ 9 | ../opensnitch/dialogs/processdetails.py \ 10 | ../opensnitch/dialogs/stats.py 11 | 12 | FORMS += ../opensnitch/res/prompt.ui \ 13 | ../opensnitch/res/ruleseditor.ui \ 14 | ../opensnitch/res/preferences.ui \ 15 | ../opensnitch/res/process_details.ui \ 16 | ../opensnitch/res/stats.ui 17 | 18 | #TSFILES contains all *.ts files in locales/ and its subfolders 19 | TSFILES := $(shell find locales/ -type f -name '*.ts') 20 | #QMFILES contains all *.qm files in locales/ and its subfolders 21 | QMFILES := $(shell find locales/ -type f -name '*.qm') 22 | #if QMFILES is empty, we set it to phony target to run unconditionally 23 | ifeq ($(QMFILES),) 24 | QMFILES := "qmfiles" 25 | endif 26 | 27 | all: $(TSFILES) $(QMFILES) 28 | 29 | #if any file from SOURCES or FORMS is older than any file from $(TSFILES) 30 | #or if opensnitch_i18n.pro was manually modified 31 | $(TSFILES): $(SOURCES) $(FORMS) opensnitch_i18n.pro 32 | @pylupdate5 opensnitch_i18n.pro 33 | 34 | #if any of the *.ts files are older that any of the *.qm files 35 | #QMFILES may also be a phony target (when no *.qm exist yet) which will always run 36 | $(QMFILES):$(TSFILES) 37 | @./generate_i18n.sh 38 | for lang in $$(ls locales/); do \ 39 | if [ ! -d ../opensnitch/i18n/$$lang ]; then mkdir -p ../opensnitch/i18n/$$lang ; fi ; \ 40 | cp locales/$$lang/opensnitch-$$lang.qm ../opensnitch/i18n/$$lang/ ; \ 41 | done 42 | -------------------------------------------------------------------------------- /ui/i18n/README.md: -------------------------------------------------------------------------------- 1 | 2 | ### Adding a new translation: 3 | 0. Install needed packages: `apt install qtchooser pyqt5-dev-tools` 4 | 1. mkdir `locales/<YOUR LOCALE>/` 5 | (echo $LANG) 6 | 2. add the path to opensnitch_i18n.pro: 7 | ``` 8 | TRANSLATIONS += locales/es_ES/opensnitch-es_ES.ts \ 9 | locales/<YOUR LOCALE>/opensnitch-<YOUR LOCALE>.ts 10 | ``` 11 | 3. make 12 | 13 | ### Updating translations: 14 | 15 | 1. update translations definitions: 16 | - pylupdate5 opensnitch_i18n.pro 17 | 18 | 2. translate a language: 19 | - linguist locales/es_ES/opensnitch-es_ES.ts 20 | 21 | 3. create .qm file: 22 | - lrelease locales/es_ES/opensnitch-es_ES.ts -qm locales/es_ES/opensnitch-es_ES.qm 23 | 24 | or: 25 | 26 | 1. make 27 | 2. linguist locales/es_ES/opensnitch-es_ES.ts 28 | 3. make 29 | 30 | ### Installing translations (manually) 31 | 32 | In order to test a new translation: 33 | 34 | `mkdir -p /usr/lib/python3/dist-packages/opensnitch/i18n/<YOUR LOCALE>/` 35 | `cp locales/<YOUR LOCALE>/opensnitch-<YOUR LOCALE>.qm /usr/lib/python3/dist-packages/opensnitch/i18n/<YOUR LOCALE>/` 36 | 37 | Note: the destination path may vary depending on your system. 38 | -------------------------------------------------------------------------------- /ui/i18n/generate_i18n.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | app_name="opensnitch" 4 | langs_dir="./locales" 5 | lrelease="lrelease" 6 | if ! command -v lrelease >/dev/null; then 7 | # on fedora 8 | lrelease="lrelease-qt5" 9 | fi 10 | 11 | #pylupdate5 opensnitch_i18n.pro 12 | 13 | for lang in $(ls $langs_dir) 14 | do 15 | lang_path="$langs_dir/$lang/$app_name-$lang" 16 | $lrelease $lang_path.ts -qm $lang_path.qm 17 | done 18 | -------------------------------------------------------------------------------- /ui/i18n/opensnitch_i18n.pro: -------------------------------------------------------------------------------- 1 | #TEMPLATE = app 2 | #TARGET = ts 3 | #INCLUDEPATH += opensnitch 4 | 5 | 6 | # Input 7 | SOURCES += ../opensnitch/service.py \ 8 | ../opensnitch/notifications.py \ 9 | ../opensnitch/customwidgets/addresstablemodel.py \ 10 | ../opensnitch/customwidgets/main.py \ 11 | ../opensnitch/dialogs/prompt/__init__.py \ 12 | ../opensnitch/dialogs/prompt/_utils.py \ 13 | ../opensnitch/dialogs/prompt/_details.py \ 14 | ../opensnitch/dialogs/prompt/_checksums.py \ 15 | ../opensnitch/dialogs/prompt/_constants.py \ 16 | ../opensnitch/dialogs/preferences.py \ 17 | ../opensnitch/dialogs/ruleseditor.py \ 18 | ../opensnitch/dialogs/processdetails.py \ 19 | ../opensnitch/dialogs/stats.py \ 20 | ../opensnitch/dialogs/firewall.py \ 21 | ../opensnitch/dialogs/firewall_rule.py 22 | 23 | FORMS += ../opensnitch/res/prompt.ui \ 24 | ../opensnitch/res/ruleseditor.ui \ 25 | ../opensnitch/res/preferences.ui \ 26 | ../opensnitch/res/process_details.ui \ 27 | ../opensnitch/res/stats.ui \ 28 | ../opensnitch/res/firewall.ui \ 29 | ../opensnitch/res/firewall_rule.ui 30 | TRANSLATIONS += locales/cs_CZ/opensnitch-cs_CZ.ts \ 31 | locales/de_DE/opensnitch-de_DE.ts \ 32 | locales/es_ES/opensnitch-es_ES.ts \ 33 | locales/eu_ES/opensnitch-eu_ES.ts \ 34 | locales/fi_FI/opensnitch-fi_FI.ts \ 35 | locales/fr_FR/opensnitch-fr_FR.ts \ 36 | locales/hu_HU/opensnitch-hu_HU.ts \ 37 | locales/hi_IN/opensnitch-hi_IN.ts \ 38 | locales/id_ID/opensnitch-id_ID.ts \ 39 | locales/it_IT/opensnitch-it_IT.ts \ 40 | locales/ja_JP/opensnitch-ja_JP.ts \ 41 | locales/lt_LT/opensnitch-lt_LT.ts \ 42 | locales/nb_NO/opensnitch-nb_NO.ts \ 43 | locales/nl_NL/opensnitch-nl_NL.ts \ 44 | locales/pt_BR/opensnitch-pt_BR.ts \ 45 | locales/ro_RO/opensnitch-ro_RO.ts \ 46 | locales/ru_RU/opensnitch-ru_RU.ts \ 47 | locales/sv_SE/opensnitch-sv_SE.ts \ 48 | locales/tr_TR/opensnitch-tr_TR.ts \ 49 | locales/uk_UA/opensnitch-uk_UA.ts \ 50 | locales/zh_TW/opensnitch-zh_TW.ts 51 | -------------------------------------------------------------------------------- /ui/opensnitch/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/__init__.py -------------------------------------------------------------------------------- /ui/opensnitch/actions/enums.py: -------------------------------------------------------------------------------- 1 | 2 | KEY_ACTIONS = "actions" 3 | KEY_NAME = "name" 4 | KEY_TYPE = "type" 5 | -------------------------------------------------------------------------------- /ui/opensnitch/actions/utils.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtGui import QColor 2 | 3 | def getColorNames(): 4 | """Return the built-in color names that can be used to choose new colors: 5 | https://doc.qt.io/qtforpython-5/PySide2/QtGui/QColor.html#predefined-colors 6 | https://www.w3.org/TR/SVG11/types.html#ColorKeywords 7 | """ 8 | return QColor.colorNames() 9 | -------------------------------------------------------------------------------- /ui/opensnitch/auth/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | import grpc 3 | 4 | Simple = "simple" 5 | TLSSimple = "tls-simple" 6 | TLSMutual = "tls-mutual" 7 | 8 | NO_CLIENT_CERT = "no-client-cert" 9 | REQ_CERT = "req-cert" 10 | REQ_ANY_CERT = "req-any-cert" 11 | VERIFY_CERT = "verify-cert" 12 | REQ_AND_VERIFY_CERT = "req-and-verify-cert" 13 | 14 | 15 | def load_file(file_path): 16 | try: 17 | with open(file_path, "rb") as f: 18 | return f.read() 19 | except Exception as e: 20 | print("auth: error loading {0}: {1}".format(file_path, e)) 21 | 22 | return None 23 | 24 | 25 | def get_tls_credentials(ca_cert, server_cert, server_key): 26 | """return a new gRPC credentials object given a server cert and key file. 27 | https://grpc.io/docs/guides/auth/#python 28 | """ 29 | try: 30 | cacert = load_file(ca_cert) 31 | cert = load_file(server_cert) 32 | cert_key = load_file(server_key) 33 | auth_nodes = False if cacert == None else True 34 | 35 | return grpc.ssl_server_credentials( 36 | ((cert_key, cert),), 37 | cacert, 38 | auth_nodes 39 | ) 40 | except Exception as e: 41 | print("get_tls_credentials error:", e) 42 | return None 43 | -------------------------------------------------------------------------------- /ui/opensnitch/customwidgets/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/customwidgets/__init__.py -------------------------------------------------------------------------------- /ui/opensnitch/customwidgets/addresstablemodel.py: -------------------------------------------------------------------------------- 1 | 2 | from PyQt5.QtSql import QSqlQuery 3 | 4 | from opensnitch.utils import AsnDB 5 | from opensnitch.customwidgets.generictableview import GenericTableModel 6 | from PyQt5.QtCore import QCoreApplication as QC 7 | 8 | class AddressTableModel(GenericTableModel): 9 | 10 | def __init__(self, tableName, headerLabels): 11 | super().__init__(tableName, headerLabels) 12 | self.asndb = AsnDB.instance() 13 | self.reconfigureColumns() 14 | 15 | def reconfigureColumns(self): 16 | self.headerLabels = [] 17 | self.setHorizontalHeaderLabels(self.headerLabels) 18 | self.headerLabels.append(QC.translate("stats", "What", "")) 19 | self.headerLabels.append(QC.translate("stats", "Hits", "")) 20 | self.headerLabels.append(QC.translate("stats", "Network name", "")) 21 | self.setHorizontalHeaderLabels(self.headerLabels) 22 | self.setColumnCount(len(self.headerLabels)) 23 | self.lastColumnCount = len(self.headerLabels) 24 | 25 | def setQuery(self, q, db): 26 | self.origQueryStr = q 27 | self.db = db 28 | 29 | if self.prevQueryStr != self.origQueryStr: 30 | self.realQuery = QSqlQuery(q, db) 31 | 32 | self.realQuery.exec_() 33 | self.realQuery.last() 34 | 35 | queryRows = max(0, self.realQuery.at()+1) 36 | self.totalRowCount = queryRows 37 | self.setRowCount(self.totalRowCount) 38 | 39 | queryColumns = self.realQuery.record().count() 40 | if self.asndb.is_available() and queryColumns < 3: 41 | self.reconfigureColumns() 42 | else: 43 | # update view's columns 44 | if queryColumns != self.lastColumnCount: 45 | self.setModelColumns(queryColumns) 46 | 47 | self.prevQueryStr = self.origQueryStr 48 | self.rowCountChanged.emit() 49 | 50 | def fillVisibleRows(self, q, upperBound, force=False): 51 | super().fillVisibleRows(q, upperBound, force) 52 | 53 | if self.asndb.is_available() == True and self.columnCount() <= 3: 54 | for n, col in enumerate(self.items): 55 | try: 56 | if len(col) < 2: 57 | continue 58 | col[2] = self.asndb.get_asn(col[0]) 59 | except: 60 | col[2] = "" 61 | finally: 62 | self.items[n] = col 63 | self.lastItems = self.items 64 | -------------------------------------------------------------------------------- /ui/opensnitch/customwidgets/netstattablemodel.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import Qt 2 | from PyQt5.QtGui import QStandardItemModel 3 | from opensnitch.customwidgets.generictableview import GenericTableModel 4 | from opensnitch.utils import sockets 5 | 6 | class NetstatTableModel(GenericTableModel): 7 | 8 | def __init__(self, tableName, headerLabels): 9 | super().__init__(tableName, headerLabels) 10 | 11 | self.COL_STATE =2 12 | self.COL_PROTO = 7 13 | self.COL_FAMILY = 10 14 | 15 | def data(self, index, role=Qt.DisplayRole): 16 | """Paint rows with the data stored in self.items""" 17 | if role == Qt.DisplayRole or role == Qt.EditRole: 18 | items_count = len(self.items) 19 | if index.isValid() and items_count > 0 and index.row() < items_count: 20 | try: 21 | # FIXME: protocol UDP + state CLOSE == state LISTEN 22 | if index.column() == self.COL_STATE: 23 | return sockets.State[self.items[index.row()][index.column()]] 24 | elif index.column() == self.COL_PROTO: 25 | return sockets.Proto[self.items[index.row()][index.column()]] 26 | elif index.column() == self.COL_FAMILY: 27 | return sockets.Family[self.items[index.row()][index.column()]] 28 | return self.items[index.row()][index.column()] 29 | except Exception as e: 30 | print("[socketsmodel] exception:", e, index.row(), index.column()) 31 | return QStandardItemModel.data(self, index, role) 32 | -------------------------------------------------------------------------------- /ui/opensnitch/customwidgets/updownbtndelegate.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | from PyQt5.QtGui import QRegion 3 | from PyQt5.QtWidgets import QItemDelegate, QAbstractItemView, QPushButton, QWidget, QVBoxLayout 4 | from PyQt5.QtCore import pyqtSignal 5 | 6 | class UpDownButtonDelegate(QItemDelegate): 7 | clicked = pyqtSignal(int, QtCore.QModelIndex) 8 | 9 | UP=-1 10 | DOWN=1 11 | 12 | def paint(self, painter, option, index): 13 | if ( 14 | isinstance(self.parent(), QAbstractItemView) 15 | and self.parent().model() is index.model() 16 | ): 17 | self.parent().openPersistentEditor(index) 18 | 19 | def createEditor(self, parent, option, index): 20 | w = QWidget(parent) 21 | w.setContentsMargins(0, 0, 0, 0) 22 | w.setAutoFillBackground(True) 23 | 24 | layout = QVBoxLayout(w) 25 | layout.setContentsMargins(0, 0, 0, 0) 26 | 27 | btnUp = QPushButton(parent) 28 | btnUp.setText("⇡") 29 | btnUp.setFlat(True) 30 | btnUp.clicked.connect(lambda: self._cb_button_clicked(self.UP, index)) 31 | 32 | btnDown = QPushButton(parent) 33 | btnDown.setText("⇣") 34 | btnDown.setFlat(True) 35 | btnDown.clicked.connect(lambda: self._cb_button_clicked(self.DOWN, index)) 36 | 37 | layout.addWidget(btnUp) 38 | layout.addWidget(btnDown) 39 | return w 40 | 41 | def _cb_button_clicked(self, action, idx): 42 | self.clicked.emit(action, idx) 43 | 44 | def updateEditorGeometry(self, editor, option, index): 45 | rect = QtCore.QRect(option.rect) 46 | minWidth = editor.minimumSizeHint().width() 47 | if rect.width() < minWidth: 48 | rect.setWidth(minWidth) 49 | editor.setGeometry(rect) 50 | # create a new mask based on the option rectangle, then apply it 51 | mask = QRegion(0, 0, option.rect.width(), option.rect.height()) 52 | editor.setProperty('offMask', mask) 53 | editor.setMask(mask) 54 | -------------------------------------------------------------------------------- /ui/opensnitch/database/enums.py: -------------------------------------------------------------------------------- 1 | 2 | class ConnFields(): 3 | Time = 0 4 | Node = 1 5 | Action = 2 6 | Protocol = 3 7 | SrcIP = 4 8 | SrcPort = 5 9 | DstIP = 6 10 | DstHost = 7 11 | DstPort = 8 12 | UID = 9 13 | PID = 10 14 | Process = 11 15 | Cmdline = 12 16 | CWD = 13 17 | Rule = 14 18 | 19 | class RuleFields(): 20 | """These fields must be in the order defined in the DB""" 21 | Time = 0 22 | Node = 1 23 | Name = 2 24 | Enabled = 3 25 | Precedence = 4 26 | Action = 5 27 | Duration = 6 28 | OpType = 7 29 | OpSensitive = 8 30 | OpOperand = 9 31 | OpData = 10 32 | Description = 11 33 | NoLog = 12 34 | Created = 13 35 | 36 | class AlertFields(): 37 | Time = 0 38 | Node = 1 39 | Type = 2 40 | Action = 3 41 | Priority = 4 42 | What = 5 43 | Body = 6 44 | Status = 7 45 | -------------------------------------------------------------------------------- /ui/opensnitch/database/migrations/upgrade_1.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE rules ADD COLUMN description; 2 | -------------------------------------------------------------------------------- /ui/opensnitch/database/migrations/upgrade_2.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE rules ADD COLUMN nolog; 2 | -------------------------------------------------------------------------------- /ui/opensnitch/database/migrations/upgrade_3.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE rules ADD COLUMN created; 2 | -------------------------------------------------------------------------------- /ui/opensnitch/dialogs/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/dialogs/__init__.py -------------------------------------------------------------------------------- /ui/opensnitch/dialogs/conndetails.py: -------------------------------------------------------------------------------- 1 | 2 | from opensnitch.nodes import Nodes 3 | from opensnitch.database import Database 4 | from opensnitch.database.enums import ConnFields 5 | from opensnitch.utils import Utils 6 | from opensnitch.utils.infowindow import InfoWindow 7 | from PyQt5.QtCore import QCoreApplication as QC 8 | 9 | class ConnDetails(InfoWindow): 10 | """Display a small dialog with the details of a connection 11 | """ 12 | 13 | def __init__(self, parent): 14 | super().__init__(parent) 15 | 16 | self._db = Database.instance() 17 | self._nodes = Nodes.instance() 18 | 19 | def showByField(self, field, value): 20 | records = self._db.get_connection_by_field(field, value) 21 | if not records.next(): 22 | return 23 | 24 | node = records.value(ConnFields.Node) 25 | uid = records.value(ConnFields.UID) 26 | if self._nodes.is_local(node): 27 | uid = Utils.get_user_id(uid) 28 | 29 | conn_text = QC.translate("stats", """ 30 | <b>{0}</b><br><br> 31 | <b>Time:</b> {1}<br><br> 32 | <b>Process:</b><br>{2}<br> 33 | <b>Cmdline:</b><br>{3}<br> 34 | <b>CWD:</b><br>{4}<br><br> 35 | <b>UID:</b> {5} <b>PID:</b> {6}<br> 36 | <br> 37 | <b>Node:</b> {7}<br><br> 38 | <b>{8}</b> {9}:{10} -> {11} ({12}):{13} 39 | <br><br> 40 | <b>Rule:</b><br> 41 | {14} 42 | """.format( 43 | records.value(ConnFields.Action).upper(), 44 | records.value(ConnFields.Time), 45 | records.value(ConnFields.Process), 46 | records.value(ConnFields.Cmdline), 47 | records.value(ConnFields.CWD), 48 | uid, 49 | records.value(ConnFields.PID), 50 | node, 51 | records.value(ConnFields.Protocol).upper(), 52 | records.value(ConnFields.SrcPort), 53 | records.value(ConnFields.SrcIP), 54 | records.value(ConnFields.DstIP), 55 | records.value(ConnFields.DstHost), 56 | records.value(ConnFields.DstPort), 57 | records.value(ConnFields.Rule) 58 | )) 59 | 60 | self.showText(conn_text) 61 | -------------------------------------------------------------------------------- /ui/opensnitch/dialogs/prompt/_checksums.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QCoreApplication as QC 2 | from opensnitch.config import Config 3 | from opensnitch.rules import Rule 4 | 5 | def verify(con, rule): 6 | """return true if the checksum of a rule matches the one of the process 7 | opening a connection. 8 | """ 9 | # when verifying checksums, we'll always have a rule type List, with at 10 | # least: path of the process + checksum 11 | if rule.operator.type != Config.RULE_TYPE_LIST: 12 | return True, "" 13 | 14 | # checksum will be empty if the daemon failed to calculate it. 15 | # in this case assume that it's ok (ignore it). 16 | if con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5] == "": 17 | return True, "" 18 | 19 | for ro in rule.operator.list: 20 | if ro.type == Config.RULE_TYPE_SIMPLE and ro.operand == Config.OPERAND_PROCESS_HASH_MD5: 21 | if ro.data != con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5]: 22 | return False, ro.data 23 | 24 | return True, "" 25 | 26 | def update_rule(node, rules, rule_name, con): 27 | """try to obtain the rule from the DB by name. 28 | return the rule on success, or None + error message on error. 29 | """ 30 | 31 | # get rule from the db 32 | records = rules.get_by_name(node, rule_name) 33 | if records == None or records.first() == False: 34 | return None, QC.translate("popups", "Rule not updated, not found by name ({0})".format(rule_name)) 35 | 36 | # transform it to proto rule 37 | rule_obj = Rule.new_from_records(records) 38 | if rule_obj.operator.type != Config.RULE_TYPE_LIST: 39 | if rule_obj.operator.operand == Config.OPERAND_PROCESS_HASH_MD5: 40 | rule_obj.operator.data = con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5] 41 | else: 42 | for op in rule_obj.operator.list: 43 | if op.operand == Config.OPERAND_PROCESS_HASH_MD5: 44 | op.data = con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5] 45 | break 46 | # add it back again to the db 47 | added = rules.add_rules(node, [rule_obj]) 48 | if not added: 49 | return None, QC.translate("popups", "Rule not updated ({0}).".format(rule_name)) 50 | 51 | return rule_obj, "" 52 | -------------------------------------------------------------------------------- /ui/opensnitch/dialogs/prompt/_constants.py: -------------------------------------------------------------------------------- 1 | from PyQt5.QtCore import QCoreApplication as QC 2 | 3 | PAGE_MAIN = 2 4 | PAGE_DETAILS = 0 5 | PAGE_CHECKSUMS = 1 6 | 7 | DEFAULT_TIMEOUT = 15 8 | 9 | # don't translate 10 | FIELD_REGEX_HOST = "regex_host" 11 | FIELD_REGEX_IP = "regex_ip" 12 | FIELD_PROC_PATH = "process_path" 13 | FIELD_PROC_ARGS = "process_args" 14 | FIELD_PROC_ID = "process_id" 15 | FIELD_USER_ID = "user_id" 16 | FIELD_DST_IP = "dst_ip" 17 | FIELD_DST_PORT = "dst_port" 18 | FIELD_DST_NETWORK = "dst_network" 19 | FIELD_DST_HOST = "simple_host" 20 | FIELD_APPIMAGE = "appimage_path" 21 | 22 | DURATION_30s = "30s" 23 | DURATION_5m = "5m" 24 | DURATION_15m = "15m" 25 | DURATION_30m = "30m" 26 | DURATION_1h = "1h" 27 | DURATION_12h = "12h" 28 | # don't translate 29 | 30 | APPIMAGE_PREFIX = "/tmp/.mount_" 31 | 32 | # label displayed in the pop-up combo 33 | DURATION_session = QC.translate("popups", "until reboot") 34 | # label displayed in the pop-up combo 35 | DURATION_forever = QC.translate("popups", "forever") 36 | -------------------------------------------------------------------------------- /ui/opensnitch/dialogs/prompt/_details.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtGui 2 | from opensnitch.config import Config 3 | 4 | def render(node, detailsWidget, con): 5 | tree = "" 6 | space = " " 7 | spaces = " " 8 | indicator = "" 9 | 10 | try: 11 | # reverse() doesn't exist on old protobuf libs. 12 | con.process_tree.reverse() 13 | except: 14 | pass 15 | for path in con.process_tree: 16 | tree = "{0}<p>│{1}\t{2}{3}{4}</p>".format(tree, path.value, spaces, indicator, path.key) 17 | spaces += " " * 4 18 | indicator = "\\_ " 19 | 20 | # XXX: table element doesn't work? 21 | details = """<b>{0}</b> {1}:{2} -> {3}:{4} 22 | <br><br> 23 | <b>Path:</b>{5}{6}<br> 24 | <b>Cmdline:</b> {7}<br> 25 | <b>CWD:</b>{8}{9}<br> 26 | <b>MD5:</b>{10}{11}<br> 27 | <b>UID:</b>{12}{13}<br> 28 | <b>PID:</b>{14}{15}<br> 29 | <br> 30 | <b>Process tree:</b><br> 31 | {16} 32 | <br> 33 | <p><b>Environment variables:<b></p> 34 | {17} 35 | """.format( 36 | con.protocol.upper(), 37 | con.src_port, con.src_ip, con.dst_ip, con.dst_port, 38 | space * 6, con.process_path, 39 | " ".join(con.process_args), 40 | space * 6, con.process_cwd, 41 | space * 7, con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5], 42 | space * 9, con.user_id, 43 | space * 9, con.process_id, 44 | tree, 45 | "".join('<p>{}={}</p>'.format(key, value) for key, value in con.process_env.items()) 46 | ) 47 | 48 | detailsWidget.document().clear() 49 | detailsWidget.document().setHtml(details) 50 | detailsWidget.moveCursor(QtGui.QTextCursor.Start) 51 | -------------------------------------------------------------------------------- /ui/opensnitch/firewall/utils.py: -------------------------------------------------------------------------------- 1 | 2 | from google.protobuf import __version__ as protobuf_version 3 | from .enums import * 4 | 5 | class Utils(): 6 | 7 | @staticmethod 8 | def isExprPort(value): 9 | """Return true if the value is valid for a port based rule: 10 | nft add rule ... tcp dport 22 accept 11 | """ 12 | return value == Statements.TCP.value or \ 13 | value == Statements.UDP.value or \ 14 | value == Statements.UDPLITE.value or \ 15 | value == Statements.SCTP.value or \ 16 | value == Statements.DCCP.value 17 | 18 | @staticmethod 19 | def isProtobufSupported(): 20 | """ 21 | The protobuf operations append() and insert() were introduced on 3.8.0 version. 22 | """ 23 | vparts = protobuf_version.split(".") 24 | return int(vparts[0]) >= 3 and int(vparts[1]) >= 8 25 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/downloader/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of OpenSnitch. 2 | # 3 | # OpenSnitch is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # OpenSnitch is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 15 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/downloader/_gui.py: -------------------------------------------------------------------------------- 1 | 2 | from PyQt5 import QtWidgets 3 | 4 | # TODO 5 | def add_panel_items(parent, config): 6 | """add an entry to the Rules Tab -> left panel, to list configured urls""" 7 | cmdPrefs = QtWidgets.QPushButton("x", objectName="cmdPrefsDownloaders") 8 | cmdPrefs.setFlat(True) 9 | #cmdPregs.clicked.connect(_cb_prefs_lists_clicked) 10 | itemTop = QtWidgets.QTreeWidgetItem(parent.rulesTreePanel) 11 | font = itemTop.font(0) 12 | font.setBold(True) 13 | itemTop.setFont(0, font) 14 | itemTop.setText(0, "lists") 15 | for idx, cfg in enumerate(config): 16 | for url in cfg['urls']: 17 | item = QtWidgets.QTreeWidgetItem(itemTop) 18 | item.setText(0, url['name']) 19 | 20 | parent.rulesTreePanel.addTopLevelItem(itemTop) 21 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/highlight/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of OpenSnitch. 2 | # 3 | # OpenSnitch is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # OpenSnitch is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 15 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/highlight/example/commonActionsDelegate.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "commonDelegateConfig", 3 | "created": "", 4 | "updated": "", 5 | "description": "customize Events tab view colors. Name of this action MUST be commonDelegateConfig for now", 6 | "actions": { 7 | "Highlight": { 8 | "enabled": true, 9 | "cells": [ 10 | { 11 | "text": [ 12 | "allow", 13 | "✓ online" 14 | ], 15 | "cols": [1, 2, 3], 16 | "color": "green", 17 | "bgcolor": "", 18 | "alignment": [ 19 | "center" 20 | ] 21 | }, 22 | { 23 | "text": [ 24 | "deny", 25 | "☓ offline" 26 | ], 27 | "cols": [1, 2, 3], 28 | "color": "red", 29 | "bgcolor": "", 30 | "alignment": [ 31 | "center" 32 | ] 33 | }, 34 | { 35 | "text": [ 36 | "reject" 37 | ], 38 | "cols": [1, 2, 3], 39 | "color": "purple", 40 | "bgcolor": "", 41 | "alignment": [ 42 | "center" 43 | ] 44 | } 45 | ], 46 | "rows": [ 47 | { 48 | "text": [ 49 | "-> 53" 50 | ], 51 | "cols": [3], 52 | "color": "black", 53 | "bgcolor": "yellow", 54 | "alignment": [] 55 | }, 56 | { 57 | "text": [ 58 | "-> 443" 59 | ], 60 | "cols": [3], 61 | "color": "white", 62 | "bgcolor": "darkRed", 63 | "alignment": [] 64 | }, 65 | { 66 | "text": [ 67 | "block-domains" 68 | ], 69 | "cols": [8], 70 | "color": "white", 71 | "bgcolor": "darkMagenta", 72 | "alignment": [] 73 | } 74 | ] 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/highlight/example/rulesActionsDelegate.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "defaultRulesDelegateConfig", 3 | "created": "", 4 | "updated": "", 5 | "description": "customize rules list. The name of this action MUST be defaultRulesDelegateConfig for now.", 6 | "actions": { 7 | "Highlight": { 8 | "enabled": true, 9 | "cells": [ 10 | { 11 | "text": ["allow", "True"], 12 | "cols": [3, 4], 13 | "color": "green", 14 | "bgcolor": "", 15 | "alignment": ["center"] 16 | }, 17 | { 18 | "text": ["deny", "False"], 19 | "cols": [3, 4], 20 | "color": "red", 21 | "bgcolor": "", 22 | "alignment": ["center"] 23 | }, 24 | { 25 | "text": ["reject"], 26 | "cols": [3, 4], 27 | "color": "purple", 28 | "bgcolor": "", 29 | "alignment": ["center"] 30 | } 31 | ], 32 | "rows": [ 33 | { 34 | "text": ["allow"], 35 | "cols": [4], 36 | "color": "white", 37 | "bgcolor": "green" 38 | }, 39 | { 40 | "text": ["deny"], 41 | "cols": [4], 42 | "color": "white", 43 | "bgcolor": "crimson" 44 | }, 45 | { 46 | "text": ["False"], 47 | "cols": [3], 48 | "color": "black", 49 | "bgcolor": "darkGray" 50 | } 51 | ] 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/sample/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of OpenSnitch. 2 | # 3 | # OpenSnitch is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # OpenSnitch is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 15 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/sample/sample.py: -------------------------------------------------------------------------------- 1 | from opensnitch.plugins import PluginBase, PluginSignal 2 | 3 | 4 | class Sample(PluginBase): 5 | # fields overriden from parent class 6 | name = "Sample" 7 | version = 0 8 | author = "opensnitch" 9 | created = "" 10 | modified = "" 11 | enabled = False 12 | description = "OpenSnitch sample plugin" 13 | 14 | # where this plugin be executed. 15 | TYPE = [PluginBase.TYPE_POPUPS] 16 | 17 | def __init__(self): 18 | self.signal_in.connect(self.cb_signal) 19 | 20 | def configure(self): 21 | pass 22 | 23 | def load_conf(self): 24 | pass 25 | 26 | def compile(self): 27 | """Transform a json object to python objects. 28 | """ 29 | print("Sample.compile()") 30 | 31 | def run(self, args): 32 | """Run the action on the given arguments. 33 | """ 34 | print("Sample.run() args:", args) 35 | 36 | def cb_signal(self, signal): 37 | print("Plugin.signal received:", self.name, signal) 38 | try: 39 | if signal['signal'] == PluginSignal.ENABLE: 40 | self.enabled = True 41 | except Exception as e: 42 | print("Plugin.Sample.cb_signal() exception:", e) 43 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/virustotal/__init__.py: -------------------------------------------------------------------------------- 1 | # This file is part of OpenSnitch. 2 | # 3 | # OpenSnitch is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 3 of the License, or 6 | # (at your option) any later version. 7 | # 8 | # OpenSnitch is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 15 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/virustotal/_procdialog.py: -------------------------------------------------------------------------------- 1 | import json 2 | from PyQt5 import QtWidgets 3 | from opensnitch.plugins.virustotal.virustotal import VTAnalysis, Virustotal 4 | from opensnitch.plugins.virustotal import _utils 5 | 6 | def build_vt_tab(plugin, parent): 7 | """add a new tab with a text field that will contain the result of the query in JSON format. 8 | """ 9 | wdg_count = parent.tabWidget.count() 10 | prev_wdg = parent.tabWidget.widget(wdg_count) 11 | if prev_wdg != None and prev_wdg.objectName() == "vt_tab": 12 | return prev_wdg 13 | 14 | wdg = QtWidgets.QWidget() 15 | wdg.setObjectName("vt_tab") 16 | gridLayout = QtWidgets.QGridLayout() 17 | textWdg = QtWidgets.QTextEdit() 18 | textWdg.setSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) 19 | gridLayout.addWidget(textWdg, 0, 0) 20 | wdg.setLayout(gridLayout) 21 | 22 | return wdg 23 | 24 | def add_vt_tab(vt, parent, widget): 25 | parent.tabWidget.addTab(widget, "Virustotal") 26 | parent.tabWidget.currentChanged.connect(lambda idx: _cb_proctab_changed(idx, vt, parent)) 27 | 28 | def _cb_proctab_changed(idx, vt, parent): 29 | cur_tab = parent.tabWidget.widget(idx) 30 | if cur_tab.objectName() != "vt_tab": 31 | return 32 | 33 | tmp = parent.labelChecksums.text().split(" ") 34 | lblChecksum = "" 35 | if len(tmp) > 1: 36 | lblChecksum = tmp[1] 37 | 38 | if lblChecksum == "": 39 | return 40 | 41 | url = vt.API_FILES + lblChecksum 42 | vt_thread = VTAnalysis(parent, vt._config, "", url, vt.API_CONNECT_TIMEOUT, vt.API_KEY, None) 43 | vt_thread.signals.completed.connect(vt.analysis_completed) 44 | vt_thread.signals.error.connect(vt.analysis_error) 45 | vt.threadsPool.start(vt_thread) 46 | 47 | def update_tab(what, response, parent, config, conn): 48 | tabs = parent.tabWidget.count()-1 49 | cur_tab = parent.tabWidget.widget(tabs) 50 | if cur_tab.objectName() != "vt_tab": 51 | return 52 | result = json.loads(response.content) 53 | textWdg = cur_tab.layout().itemAtPosition(0, 0).widget() 54 | textWdg.clear() 55 | 56 | if result.get('data') == None: 57 | textWdg.setPlainText("checksum not found on Virustotal. Upload the binary for analysis and generate a report.") 58 | return 59 | textWdg.setHtml(_utils.report_to_html(result)) 60 | -------------------------------------------------------------------------------- /ui/opensnitch/plugins/virustotal/example/virustotal.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "virustotal", 3 | "created": "", 4 | "updated": "", 5 | "description": "analyze connections with Virustotal", 6 | "type": ["popups", "proc-dialog"], 7 | "actions": { 8 | "virustotal": { 9 | "enabled": true, 10 | "config": { 11 | "api_timeout": 2, 12 | "api_key": "https://virustotal.readme.io/docs/please-give-me-an-api-key", 13 | "api_domains_url": "https://www.virustotal.com/api/v3/domains/", 14 | "api_ips_url": "https://www.virustotal.com/api/v3/ip_addresses/", 15 | "api_files_url": "https://www.virustotal.com/api/v3/files/" 16 | }, 17 | "check": ["domains", "ips", "hashes"] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ui/opensnitch/proto/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Simone Margaritelli 2 | # 2019-2025 Gustavo Iñiguez Goia 3 | # 4 | # This file is part of OpenSnitch. 5 | # 6 | # OpenSnitch is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, either version 3 of the License, or 9 | # (at your option) any later version. 10 | # 11 | # OpenSnitch is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | # GNU General Public License for more details. 15 | # 16 | # You should have received a copy of the GNU General Public License 17 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 18 | 19 | from packaging.version import Version 20 | import importlib 21 | from opensnitch.utils import Versions 22 | 23 | # Protobuffers compiled with protobuf < 3.20.0 are incompatible with 24 | # protobuf >= 4.0.0 25 | # https://github.com/evilsocket/opensnitch/wiki/GUI-known-problems#gui-does-not-show-up 26 | # 27 | # In order to solve this issue, we provide several protobuffers: 28 | # proto.ui_pb2* for protobuf >= 4.0.0 29 | # proto.pre3200.ui_pb2* for protobuf >= 3.6.0 and < 3.20.0 30 | # 31 | # To avoid import errors, each protobuffer must be placed in its own directory, 32 | # and the name of the protobuffer files must be named with the syntax 33 | # <prefix>_pb2.py/<prefix>_pb2_grpc.py: 34 | # ui_pb2.py and ui_pb2_grpc.py 35 | 36 | default_pb = "opensnitch.proto.ui_pb2" 37 | default_grpc = "opensnitch.proto.ui_pb2_grpc" 38 | old_pb = "opensnitch.proto.pre3200.ui_pb2" 39 | old_grpc = "opensnitch.proto.pre3200.ui_pb2_grpc" 40 | 41 | def import_(): 42 | """load the protobuffer needed based on the grpc and protobuffer version 43 | installed in the system. 44 | """ 45 | try: 46 | gui_version, grpc_version, proto_version = Versions.get() 47 | proto_ver = default_pb 48 | grpc_ver = default_grpc 49 | 50 | if Version(proto_version) < Version("3.20.0"): 51 | proto_ver = old_pb 52 | grpc_ver = old_grpc 53 | 54 | return importlib.import_module(proto_ver), importlib.import_module(grpc_ver) 55 | except Exception as e: 56 | print("error importing protobuffer: ", repr(e)) 57 | return importlib.import_module(default_pb, default_grpc) 58 | -------------------------------------------------------------------------------- /ui/opensnitch/res/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/__init__.py -------------------------------------------------------------------------------- /ui/opensnitch/res/icon-alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/icon-alert.png -------------------------------------------------------------------------------- /ui/opensnitch/res/icon-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/icon-off.png -------------------------------------------------------------------------------- /ui/opensnitch/res/icon-pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/icon-pause.png -------------------------------------------------------------------------------- /ui/opensnitch/res/icon-red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/icon-red.png -------------------------------------------------------------------------------- /ui/opensnitch/res/icon-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/icon-white.png -------------------------------------------------------------------------------- /ui/opensnitch/res/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/opensnitch/res/icon.png -------------------------------------------------------------------------------- /ui/opensnitch/res/resources.qrc: -------------------------------------------------------------------------------- 1 | <RCC> 2 | <qresource prefix="pics"> 3 | <file>icon-white.svg</file> 4 | <file>icon-white.png</file> 5 | <file>icon-red.png</file> 6 | <file>icon.png</file> 7 | </qresource> 8 | </RCC> 9 | -------------------------------------------------------------------------------- /ui/opensnitch/res/themes/dark/icons/LICENSE: -------------------------------------------------------------------------------- 1 | These files are part of the HighContrast theme: 2 | https://download.gnome.org/sources/gnome-themes-standard/ 3 | 4 | Copyright: Copyright (C) 2010 Aron Xu <aronxu@gnome.org> 5 | Copyright (C) 2010 A S Alam <aalam@users.sf.net> 6 | Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org> 7 | Copyright (C) 2010 Daniel Nylander <po@danielnylander.se> 8 | Copyright (C) 2010 Fran Diéguez <fran.dieguez@mabishu.com> 9 | Copyright (C) 2010 Gheyret T.Kenji <gheyret@gmail.com> 10 | Copyright (C) 2010 Ivar Smolin <okul@linux.ee> 11 | Copyright (C) 2010 Jakub Steiner <jimmac@gmail.com> 12 | Copyright (C) 2010 Jorge González <jorgegonz@svn.gnome.org> 13 | Copyright (C) 2010 Kenneth Nielsen <k.nielsen81@gmail.com> 14 | Copyright (C) 2010 Kjartan Maraas <kmaraas@gnome.org> 15 | Copyright (C) 2010 Kris Thomsen <lakristho@gmail.com> 16 | Copyright (C) 2010 Lapo Calamandrei <calamandrei@gmail.com> 17 | Copyright (C) 2010 Lucian Adrian Grijincu <lucian.grijincu@gmail.com> 18 | Copyright (C) 2010 Matej Urbančič <mateju@svn.gnome.org> 19 | Copyright (C) 2010 Matthias Clasen <mclasen@redhat.com> 20 | Copyright (C) 2010 Priit Laes <plaes@plaes.org> 21 | Copyright (C) 2010 Theodore Dimitriadis <liakoni@gmail.com> 22 | Copyright (C) 2010 Theppitak Karoonboonyanan <thep@linux.thai.net> 23 | Copyright (C) 2010 William Jon McCann <jmccann@redhat.com> 24 | Copyright (C) 2010 Yaron Shahrabani <sh.yaron@gmail.com> 25 | Copyright (C) 2010 Hylke Bons 26 | Copyright (C) 2012 Red Hat, Inc 27 | Copyright (C) Bill Haneman 28 | Copyright (C) T. Liebeck 29 | License: LGPL-2.1+ 30 | This library is free software; you can redistribute it and/or modify it 31 | under the terms of the GNU Lesser General Public License as published by the 32 | Free Software Foundation; either version 2.1 of the License, or (at your 33 | option) any later version. 34 | . 35 | This library is distributed in the hope that it will be useful, but WITHOUT 36 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 37 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 38 | for more details. 39 | . 40 | You should have received a copy of the GNU Lesser General Public 41 | License along with this library; if not, write to the 42 | Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 43 | 02110-1301 USA. 44 | . 45 | See /usr/share/common-licenses/LGPL-2.1 on your debian system. 46 | -------------------------------------------------------------------------------- /ui/opensnitch/res/themes/dark/icons/list-remove.svg: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 | <!-- Generator: Adobe Illustrator 9.0, SVG Export Plug-In --> 3 | <svg 4 | xmlns:dc="http://purl.org/dc/elements/1.1/" 5 | xmlns:cc="http://web.resource.org/cc/" 6 | xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 7 | xmlns:svg="http://www.w3.org/2000/svg" 8 | xmlns="http://www.w3.org/2000/svg" 9 | xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd" 10 | xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" 11 | width="48.000000px" 12 | height="48.000000px" 13 | viewBox="0 0 48 48" 14 | xml:space="preserve" 15 | id="svg2" 16 | sodipodi:version="0.32" 17 | inkscape:version="0.42" 18 | sodipodi:docname="list-remove.svg" 19 | sodipodi:docbase="/home/luca/Desktop/black-and-white/scalable/actions"><metadata 20 | id="metadata19"><rdf:RDF><cc:Work 21 | rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type 22 | rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs 23 | id="defs17" /><sodipodi:namedview 24 | inkscape:window-height="950" 25 | inkscape:window-width="1280" 26 | inkscape:pageshadow="2" 27 | inkscape:pageopacity="0.0" 28 | borderopacity="1.0" 29 | bordercolor="#666666" 30 | pagecolor="#ffffff" 31 | id="base" 32 | showguides="true" 33 | showgrid="true" 34 | inkscape:grid-bbox="true" 35 | inkscape:grid-points="true" 36 | inkscape:zoom="15.875000" 37 | inkscape:cx="24.000000" 38 | inkscape:cy="24.000000" 39 | inkscape:window-x="0" 40 | inkscape:window-y="25" 41 | inkscape:current-layer="svg2" /> 42 | <rect 43 | style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:none;stroke-width:1.0000000px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000" 44 | id="rect3315" 45 | width="48.000000" 46 | height="48.000000" 47 | x="0.0000000" 48 | y="0.0000000" /><g 49 | id="Layer_x0020_4" 50 | style="fill-rule:nonzero;stroke:#000000;stroke-miterlimit:4.0000000" 51 | transform="translate(0.000000,-2.000000)"> 52 | <g 53 | style="stroke:none" 54 | id="g5"> 55 | <path 56 | style="fill:none;stroke:#ffffff;stroke-width:8.0000000;stroke-linejoin:round;stroke-miterlimit:4.0000000;stroke-dasharray:none" 57 | d="M 42.000000,21.000000 L 6.0000000,21.000000 L 6.0000000,31.000000 L 42.000000,31.000000 L 42.000000,21.000000 z " 58 | id="path7" 59 | sodipodi:nodetypes="ccccc" /> 60 | 61 | <path 62 | d="M 6.0000000,31.000000 L 6.0000000,21.000000 L 42.000000,21.000000 L 42.000000,31.000000 L 6.0000000,31.000000 z " 63 | id="path11" 64 | sodipodi:nodetypes="ccccc" /> 65 | </g> 66 | </g> 67 | 68 | </svg> -------------------------------------------------------------------------------- /ui/opensnitch/utils/duration/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2018 Simone Margaritelli 2 | # 2018 MiWCryptAnalytics 3 | # 2023 munix9 4 | # 2023 Wojtek Widomski 5 | # 2019-2025 Gustavo Iñiguez Goia 6 | # 7 | # This file is part of OpenSnitch. 8 | # 9 | # OpenSnitch is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 3 of the License, or 12 | # (at your option) any later version. 13 | # 14 | # OpenSnitch is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 21 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/duration/duration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (C) 2018 Simone Margaritelli 4 | # 2018 MiWCryptAnalytics 5 | # 2023 munix9 6 | # 2023 Wojtek Widomski 7 | # 2019-2025 Gustavo Iñiguez Goia 8 | # 9 | # This file is part of OpenSnitch. 10 | # 11 | # OpenSnitch is free software: you can redistribute it and/or modify 12 | # it under the terms of the GNU General Public License as published by 13 | # the Free Software Foundation, either version 3 of the License, or 14 | # (at your option) any later version. 15 | # 16 | # OpenSnitch is distributed in the hope that it will be useful, 17 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 18 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 | # GNU General Public License for more details. 20 | # 21 | # You should have received a copy of the GNU General Public License 22 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 23 | 24 | 25 | import re 26 | 27 | r = re.compile(r'(\d+)([smhdw]+)') 28 | 29 | _second = 1 30 | _minute = 60 31 | _hour = 60 * _minute 32 | _day = 60 * _hour 33 | _week = 60 * _day 34 | 35 | _units = { 36 | 's': _second, 37 | 'm': _minute, 38 | 'h': _hour, 39 | 'd': _day, 40 | 'w': _week 41 | } 42 | 43 | def to_seconds(dur_str): 44 | """converts a Golang duration string to seconds: 45 | "20s" -> 20 seconds 46 | "2m" -> 120 seconds 47 | ... 48 | """ 49 | secs = 0 50 | try: 51 | finds = r.findall(dur_str) 52 | for d in finds: 53 | try: 54 | unit = _units[d[1]] 55 | secs += (unit * int(d[0])) 56 | except: 57 | print("duration.to_seconds(): invalid unit:", d) 58 | 59 | return secs 60 | except: 61 | return secs 62 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/infowindow.py: -------------------------------------------------------------------------------- 1 | 2 | from opensnitch.config import Config 3 | from PyQt5 import QtCore, QtWidgets, QtGui 4 | 5 | class InfoWindow(QtWidgets.QDialog): 6 | """Display a text on a small dialog. 7 | """ 8 | def __init__(self, parent): 9 | QtWidgets.QDialog.__init__(self, parent, QtCore.Qt.Tool) 10 | self.setContentsMargins(0, 0, 0, 0) 11 | 12 | self._cfg = Config.get() 13 | 14 | self.layout = QtWidgets.QVBoxLayout(self) 15 | self._textedit = QtWidgets.QTextEdit() 16 | # hide cursor 17 | self._textedit.setCursorWidth(0) 18 | self._textedit.setViewportMargins(QtCore.QMargins(0,0,0,0)) 19 | self._textedit.setMinimumSize(300, 325) 20 | self._textedit.setReadOnly(True) 21 | self._textedit.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse | QtCore.Qt.TextSelectableByKeyboard) 22 | self._textedit.setAutoFillBackground(True) 23 | self._textedit.setStyleSheet("QLabel { background: yellow }") 24 | 25 | self.layout.addWidget(self._textedit) 26 | 27 | self._load_settings() 28 | 29 | def closeEvent(self, ev): 30 | self._save_settings() 31 | ev.accept() 32 | self.hide() 33 | 34 | def _load_settings(self): 35 | saved_geometry = self._cfg.getSettings(Config.INFOWIN_GEOMETRY) 36 | if saved_geometry is not None: 37 | self.restoreGeometry(saved_geometry) 38 | 39 | def _save_settings(self): 40 | self._cfg.setSettings(Config.INFOWIN_GEOMETRY, self.saveGeometry()) 41 | 42 | def showText(self, text): 43 | self._load_settings() 44 | 45 | self._textedit.setText(text) 46 | #self.resize(self.tooltip_textedit.sizeHint()) 47 | 48 | pos = QtGui.QCursor.pos() 49 | win_size = self.size() 50 | # center dialog on cursor, relative to the parent widget. 51 | x_off = (int(win_size.width()/2)) 52 | y_off = (int(win_size.height()/2)) 53 | point = QtCore.QPoint( 54 | pos.x()-x_off, pos.y()-y_off 55 | ) 56 | self.move(point.x(), point.y()) 57 | 58 | self.show() 59 | 60 | def showHtml(self, text): 61 | self._load_settings() 62 | 63 | self._textedit.setHtml(text) 64 | 65 | pos = QtGui.QCursor.pos() 66 | win_size = self.size() 67 | # center dialog on cursor, relative to the parent widget. 68 | x_off = (int(win_size.width()/2)) 69 | y_off = (int(win_size.height()/2)) 70 | point = QtCore.QPoint( 71 | pos.x()-x_off, pos.y()-y_off 72 | ) 73 | self.move(point.x(), point.y()) 74 | 75 | self.show() 76 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/languages.py: -------------------------------------------------------------------------------- 1 | from PyQt5 import QtCore 2 | import os 3 | 4 | from opensnitch.config import Config 5 | 6 | DEFAULT_LANG = "en" 7 | DEFAULT_LANGNAME = "English" 8 | 9 | def __get_i18n_path(): 10 | return os.path.dirname(os.path.realpath(__file__)) + "/../i18n" 11 | 12 | def init(saved_lang): 13 | locale = QtCore.QLocale.system() 14 | lang = locale.name() 15 | if saved_lang: 16 | lang = saved_lang 17 | i18n_path = __get_i18n_path() 18 | print("Loading translations:", i18n_path, "locale:", lang) 19 | translator = QtCore.QTranslator() 20 | translator.load(i18n_path + "/" + lang + "/opensnitch-" + lang + ".qm") 21 | 22 | return translator 23 | 24 | def save(cfg, lang): 25 | q = QtCore.QLocale(lang) 26 | langname = q.nativeLanguageName().capitalize() 27 | if lang == DEFAULT_LANG: 28 | langname = DEFAULT_LANGNAME 29 | cfg.setSettings(Config.DEFAULT_LANGUAGE, lang) 30 | cfg.setSettings(Config.DEFAULT_LANGNAME, langname) 31 | 32 | def get_all(): 33 | langs = [DEFAULT_LANG] 34 | names = [DEFAULT_LANGNAME] 35 | i18n_path = __get_i18n_path() 36 | lang_dirs = os.listdir(i18n_path) 37 | lang_dirs.sort() 38 | for lang in lang_dirs: 39 | q = QtCore.QLocale(lang) 40 | langs.append(lang) 41 | names.append(q.nativeLanguageName()) 42 | return langs, names 43 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/network_aliases/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 Nolan Carouge 2 | # 3 | # This file is part of OpenSnitch. 4 | # 5 | # OpenSnitch is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # OpenSnitch is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 17 | 18 | from .network_aliases import NetworkAliases 19 | 20 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/network_aliases/network_aliases.json: -------------------------------------------------------------------------------- 1 | { 2 | "LAN": [ 3 | "10.0.0.0/8", 4 | "172.16.0.0/12", 5 | "192.168.0.0/16", 6 | "127.0.0.0/8", 7 | "::1", 8 | "fc00::/7" 9 | ], 10 | "MULTICAST": [ 11 | "224.0.0.0/4", 12 | "ff00::/8" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/network_aliases/network_aliases.py: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2024 Nolan Carouge 2 | # 3 | # This file is part of OpenSnitch. 4 | # 5 | # OpenSnitch is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 3 of the License, or 8 | # (at your option) any later version. 9 | # 10 | # OpenSnitch is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with OpenSnitch. If not, see <http://www.gnu.org/licenses/>. 17 | 18 | import json 19 | import ipaddress 20 | import os 21 | 22 | class NetworkAliases: 23 | ALIASES = {} 24 | 25 | @staticmethod 26 | def load_aliases(): 27 | # Define the path to the network_aliases.json file 28 | script_dir = os.path.dirname(os.path.abspath(__file__)) 29 | filename = os.path.join(script_dir, 'network_aliases.json') 30 | 31 | # Check if the file exists before attempting to load it 32 | if not os.path.exists(filename): 33 | raise FileNotFoundError(f"The file '{filename}' does not exist.") 34 | 35 | # Load the JSON file 36 | with open(filename, 'r') as f: 37 | NetworkAliases.ALIASES = json.load(f) 38 | print(f"Loaded network aliases from {filename}") # Confirmation message 39 | 40 | @staticmethod 41 | def get_alias(ip): 42 | try: 43 | ip_obj = ipaddress.ip_address(ip) 44 | for alias, networks in NetworkAliases.ALIASES.items(): 45 | for network in networks: 46 | net_obj = ipaddress.ip_network(network) 47 | if ip_obj in net_obj: 48 | return alias 49 | except ValueError: 50 | pass 51 | return None 52 | 53 | @staticmethod 54 | def get_networks_for_alias(alias): 55 | return NetworkAliases.ALIASES.get(alias, []) 56 | 57 | @staticmethod 58 | def get_alias_all(): 59 | # Return a list of all alias names 60 | return list(NetworkAliases.ALIASES.keys()) 61 | 62 | # Load aliases at startup 63 | try: 64 | NetworkAliases.load_aliases() 65 | except FileNotFoundError as e: 66 | print(e) 67 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/qvalidator.py: -------------------------------------------------------------------------------- 1 | 2 | from PyQt5 import QtCore, QtGui 3 | 4 | class RestrictChars(QtGui.QValidator): 5 | result = QtCore.pyqtSignal(object) 6 | 7 | def __init__(self, restricted_chars, *args, **kwargs): 8 | QtGui.QValidator.__init__(self, *args, **kwargs) 9 | self._restricted_chars = restricted_chars 10 | 11 | def validate(self, value, pos): 12 | # allow to delete all characters 13 | if len(value) == 0: 14 | return QtGui.QValidator.Intermediate, value, pos 15 | 16 | # user can type characters or paste them. 17 | # pos value when pasting can be any number, depending on where did the 18 | # user paste the characters. 19 | for char in self._restricted_chars: 20 | if char in value: 21 | self.result.emit(QtGui.QValidator.Invalid) 22 | return QtGui.QValidator.Invalid, value, pos 23 | 24 | self.result.emit(QtGui.QValidator.Acceptable) 25 | return QtGui.QValidator.Acceptable, value, pos 26 | -------------------------------------------------------------------------------- /ui/opensnitch/utils/sockets.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # https://pkg.go.dev/syscall#pkg-constants 4 | Family = { 5 | '0': 'AF_UNSPEC', 6 | '2': 'AF_INET', 7 | '10': 'AF_INET6', 8 | '17': 'AF_PACKET', 9 | '40': 'AF_VSOCK', 10 | '44': 'AF_XDP', 11 | '45': 'AF_MCTP', 12 | } 13 | 14 | Proto = { 15 | '0': 'IP', 16 | '1': 'ICMP', 17 | '2': 'IGMP', 18 | '6': 'TCP', 19 | '17': 'UDP', 20 | '33': 'DCCP', 21 | '41': 'IPv6', 22 | '58': 'ICMPv6', 23 | '132': 'SCTP', 24 | '136': 'UDPLITE', 25 | '255': 'RAW', 26 | '3': 'ETH_P_ALL', 27 | '2048': 'ETH_P_IP', 28 | '34525': 'ETH_P_IPV6', 29 | '2054': 'ETH_P_ARP', 30 | '32821': 'ETH_P_RARP', 31 | '33024': 'ETH_P_8021Q', 32 | '4': 'ETH_P_802_2', 33 | '34916': 'ETH_P_PPPOE', 34 | '34958': 'ETH_P_PAE', 35 | '35085': 'ETH_P_FCOE' 36 | } 37 | 38 | State = { 39 | # special case for protos that don't report state (AF_PACKET) 40 | '0': 'LISTEN', 41 | '1': 'Established', 42 | '2': 'TCP_SYN_SENT', 43 | '3': 'TCP_SYN_RECV', 44 | '4': 'TCP_FIN_WAIT1', 45 | '5': 'TCP_FIN_WAIT2', 46 | '6': 'TCP_TIME_WAIT', 47 | '7': 'CLOSE', 48 | '8': 'TCP_CLOSE_WAIT', 49 | '9': 'TCP_LAST_ACK', 50 | '10': 'LISTEN', 51 | '11': 'TCP_CLOSING', 52 | '12': 'TCP_NEW_SYNC_RECV', 53 | '13': 'TCP_MAX_STATES' 54 | } 55 | -------------------------------------------------------------------------------- /ui/opensnitch/version.py: -------------------------------------------------------------------------------- 1 | version = '1.7.0' 2 | -------------------------------------------------------------------------------- /ui/requirements.txt: -------------------------------------------------------------------------------- 1 | grpcio-tools>=1.10.1 2 | pyinotify==0.9.6 3 | unicode_slugify==0.1.5 4 | pyqt5>=5.6 5 | protobuf 6 | -------------------------------------------------------------------------------- /ui/resources/icons/48x48/opensnitch-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/resources/icons/48x48/opensnitch-ui.png -------------------------------------------------------------------------------- /ui/resources/icons/64x64/opensnitch-ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/resources/icons/64x64/opensnitch-ui.png -------------------------------------------------------------------------------- /ui/resources/io.github.evilsocket.opensnitch.appdata.xml: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <component type="desktop-application"> 3 | <id>io.github.evilsocket.opensnitch</id> 4 | 5 | <name>OpenSnitch</name> 6 | <summary>GNU/Linux interactive application firewall</summary> 7 | 8 | <metadata_license>FTL</metadata_license> 9 | <project_license>GPL-3.0-or-later</project_license> 10 | 11 | <supports> 12 | <control>pointing</control> 13 | <control>keyboard</control> 14 | <control>touch</control> 15 | </supports> 16 | 17 | <description> 18 | <p> 19 | Whenever a program tries to establish a new connection, it'll prompt the user to allow or deny it. 20 | </p> 21 | <p> 22 | 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. 23 | </p> 24 | <p> 25 | The GUI allows the user to view live outgoing connections, as well as search by process, user, host or port. 26 | </p> 27 | <p> 28 | OpenSnitch can also work as a system-wide domains blocker, by using lists of domains, list of IPs or list of regular expressions. 29 | </p> 30 | </description> 31 | 32 | <categories> 33 | <category>System</category> 34 | <category>Security</category> 35 | <category>Monitor</category> 36 | <category>Network</category> 37 | </categories> 38 | 39 | <icon type="stock">opensnitch-ui</icon> 40 | <url type="homepage">https://github.com/evilsocket/opensnitch</url> 41 | <url type="bugtracker">https://github.com/evilsocket/opensnitch/issues</url> 42 | <url type="help">https://github.com/evilsocket/opensnitch/wiki</url> 43 | <launchable type="desktop-id">opensnitch_ui.desktop</launchable> 44 | <screenshots> 45 | <screenshot type="default"> 46 | <image>https://user-images.githubusercontent.com/2742953/85205382-6ba9cb00-b31b-11ea-8e9a-bd4b8b05a236.png</image> 47 | </screenshot> 48 | <screenshot> 49 | <image>https://user-images.githubusercontent.com/2742953/217039798-3477c6c2-d64f-4eea-89af-cd94ee77cff4.png</image> 50 | </screenshot> 51 | <screenshot> 52 | <image>https://user-images.githubusercontent.com/2742953/99863173-3987e800-2b9d-11eb-93f2-fe3121b18c51.png</image> 53 | </screenshot> 54 | </screenshots> 55 | <content_rating type="oars-1.0" /> 56 | </component> 57 | -------------------------------------------------------------------------------- /ui/resources/kcm_opensnitch.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Exec=opensnitch-ui 3 | Icon=opensnitch-ui 4 | Type=Service 5 | X-KDE-ServiceTypes=SystemSettingsExternalApp 6 | Name=OpenSnitch Firewall 7 | Comment=OpenSnitch Firewall Graphical Interface 8 | X-KDE-Keywords=system,firewall,policies,security,polkit,policykit,douane 9 | X-KDE-Autostart-after=panel 10 | -------------------------------------------------------------------------------- /ui/resources/opensnitch_ui.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Name=OpenSnitch 4 | Exec=opensnitch-ui 5 | Icon=opensnitch-ui 6 | GenericName=OpenSnitch Firewall 7 | GenericName[hu]=OpenSnitch-tűzfal 8 | GenericName[nb]=OpenSnitch brannmur 9 | Comment=Interactive application firewall 10 | Comment[es]=Firewall de aplicaciones 11 | Comment[hu]=Alkalmazási tűzfal 12 | Comment[nb]=Interaktiv programbrannmur 13 | Terminal=false 14 | NoDisplay=false 15 | Categories=System;Security;Monitor;Network; 16 | Keywords=system;firewall;policies;security;polkit;policykit; 17 | X-GNOME-Autostart-Delay=3 18 | X-GNOME-Autostart-enabled=true 19 | -------------------------------------------------------------------------------- /ui/setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | import os 4 | import sys 5 | 6 | path = os.path.abspath(os.path.dirname(__file__)) 7 | sys.path.append(path) 8 | 9 | from opensnitch.version import version 10 | 11 | setup(name='opensnitch-ui', 12 | version=version, 13 | description='Prompt service and UI for the opensnitch interactive firewall application.', 14 | long_description='GUI for the opensnitch interactive firewall application\n\ 15 | opensnitch-ui is a GUI for opensnitch written in Python.\n\ 16 | It allows the user to view live outgoing connections, as well as search\n\ 17 | to make connections.\n\ 18 | .\n\ 19 | The user can decide if block the outgoing connection based on properties of\n\ 20 | the connection: by port, by uid, by dst ip, by program or a combination\n\ 21 | of them.\n\ 22 | .\n\ 23 | These rules can last forever, until the app restart or just one time.', 24 | url='https://github.com/evilsocket/opensnitch', 25 | author='Simone "evilsocket" Margaritelli', 26 | author_email='evilsocket@protonmail.com', 27 | license='GPL-3.0', 28 | packages=find_packages(), 29 | include_package_data = True, 30 | package_data={'': ['*.*']}, 31 | data_files=[('/usr/share/applications', ['resources/opensnitch_ui.desktop']), 32 | ('/usr/share/kservices5', ['resources/kcm_opensnitch.desktop']), 33 | ('/usr/share/icons/hicolor/scalable/apps', ['resources/icons/opensnitch-ui.svg']), 34 | ('/usr/share/icons/hicolor/48x48/apps', ['resources/icons/48x48/opensnitch-ui.png']), 35 | ('/usr/share/icons/hicolor/64x64/apps', ['resources/icons/64x64/opensnitch-ui.png']), 36 | ('/usr/share/metainfo', ['resources/io.github.evilsocket.opensnitch.appdata.xml'])], 37 | scripts = [ 'bin/opensnitch-ui' ], 38 | zip_safe=False) 39 | -------------------------------------------------------------------------------- /ui/tests/README.md: -------------------------------------------------------------------------------- 1 | GUI unit tests. 2 | 3 | We use pytest [0] to pytest-qt [1] to test GUI code. 4 | 5 | To run the tests: `cd tests; pytest -v` 6 | 7 | TODO: 8 | - test service class (Service.py) 9 | - test events window (stats.py): 10 | - The size of the window must be saved on close, and restored when opening it again. 11 | - Columns width of every view must be saved and restored properly. 12 | - On the Events tab, clicking on the Node, Process or Rule column must jump to the detailed view of the selected item. 13 | - When entering into a detail view: 14 | - the results limit configured must be respected (that little button on the bottom right of every tab). 15 | - must apply the proper SQL query for every detailed view. 16 | - When going back from a detail view: 17 | - The SQL query must be restored. 18 | - Test rules context menu actions. 19 | - Test select rows and copy them to the clipboard (ctrl+c). 20 | 21 | 22 | 0. https://docs.pytest.org/en/6.2.x/ 23 | 1. https://pytest-qt.readthedocs.io/en/latest/intro.html 24 | -------------------------------------------------------------------------------- /ui/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evilsocket/opensnitch/856c83f84b7fce8b236b67adce37b4f75206baca/ui/tests/__init__.py -------------------------------------------------------------------------------- /ui/tests/dialogs/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | from opensnitch.database import Database 3 | from opensnitch.config import Config 4 | from opensnitch.nodes import Nodes 5 | 6 | # grpc object 7 | class ClientConfig: 8 | version = "1.2.3" 9 | name = "bla" 10 | logLevel = 0 11 | isFirewallRunning = False 12 | rules = [] 13 | config = '''{ 14 | "Server":{ 15 | "Address": "unix:///tmp/osui.sock", 16 | "LogFile": "/var/log/opensnitchd.log" 17 | }, 18 | "DefaultAction": "deny", 19 | "DefaultDuration": "once", 20 | "InterceptUnknown": false, 21 | "ProcMonitorMethod": "ebpf", 22 | "LogLevel": 0, 23 | "LogUTC": true, 24 | "LogMicro": false, 25 | "Firewall": "iptables", 26 | "Stats": { 27 | "MaxEvents": 150, 28 | "MaxStats": 50 29 | } 30 | } 31 | ''' 32 | 33 | class Connection: 34 | protocol = "tcp" 35 | src_ip = "127.0.0.1" 36 | src_port = "12345" 37 | dst_ip = "127.0.0.1" 38 | dst_host = "localhost" 39 | dst_port = "54321" 40 | user_id = 1000 41 | process_id = 9876 42 | process_path = "/bin/cmd" 43 | process_cwd = "/tmp" 44 | process_args = "/bin/cmd --parm1 test" 45 | process_env = [] 46 | 47 | db = Database.instance() 48 | db.initialize() 49 | Config.init() 50 | 51 | nodes = Nodes.instance() 52 | nodes._nodes["unix:/tmp/osui.sock"] = { 53 | 'data': ClientConfig 54 | } 55 | -------------------------------------------------------------------------------- /utils/legacy/make_ads_rules.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import re 3 | import ipaddress 4 | import datetime 5 | import os 6 | 7 | lists = ( \ 8 | "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts", 9 | "https://mirror1.malwaredomains.com/files/justdomains", 10 | "https://sysctl.org/cameleon/hosts", 11 | "https://zeustracker.abuse.ch/blocklist.php?download=domainblocklist", 12 | "https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt", 13 | "https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt", 14 | "https://hosts-file.net/ad_servers.txt" ) 15 | 16 | domains = {} 17 | 18 | for url in lists: 19 | print "Downloading %s ..." % url 20 | r = requests.get(url) 21 | if r.status_code != 200: 22 | print "Error, status code %d" % r.status_code 23 | continue 24 | 25 | for line in r.text.split("\n"): 26 | line = line.strip() 27 | if line == "": 28 | continue 29 | 30 | elif line[0] == "#": 31 | continue 32 | 33 | for part in re.split(r'\s+', line): 34 | part = part.strip() 35 | if part == "": 36 | continue 37 | 38 | try: 39 | duh = ipaddress.ip_address(part) 40 | except ValueError: 41 | if part != "localhost": 42 | domains[part] = 1 43 | 44 | print "Got %d unique domains, saving as rules to ./rules/ ..." % len(domains) 45 | 46 | os.system("mkdir -p rules") 47 | 48 | idx = 0 49 | for domain, _ in domains.iteritems(): 50 | with open("rules/adv-%d.json" % idx, "wt") as fp: 51 | tpl = """ 52 | { 53 | "created": "%s", 54 | "updated": "%s", 55 | "name": "deny-adv-%d", 56 | "enabled": true, 57 | "action": "deny", 58 | "duration": "always", 59 | "operator": { 60 | "type": "simple", 61 | "operand": "dest.host", 62 | "data": "%s" 63 | } 64 | }""" 65 | now = datetime.datetime.utcnow().isoformat("T") + "Z" 66 | data = tpl % ( now, now, idx, domain ) 67 | fp.write(data) 68 | 69 | idx = idx + 1 70 | -------------------------------------------------------------------------------- /utils/packaging/build_modules.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # opensnitch - 2022-2023 4 | # 5 | echo """ 6 | 7 | Dependencies needed to compile the eBPF modules: 8 | sudo apt install -y wget flex bison ca-certificates wget python3 rsync bc libssl-dev clang llvm libelf-dev libzip-dev git libpcap-dev 9 | --- 10 | """ 11 | 12 | kernel_version=$(uname -r | cut -d. -f1,2) 13 | if [ ! -z $1 ]; then 14 | kernel_version=$1 15 | fi 16 | 17 | kernel_sources="v${kernel_version}.tar.gz" 18 | 19 | if [ -f "${kernel_sources}" ]; then 20 | echo -n "[i] Deleting previous kernel sources ${kernel_sources}: " 21 | rm -f ${kernel_sources} && echo "OK" || echo "ERROR" 22 | fi 23 | echo "[+] Downloading kernel sources:" 24 | wget -nv --show-progress https://github.com/torvalds/linux/archive/${kernel_sources} 1>/dev/null 25 | echo 26 | 27 | if [ -d "linux-${kernel_version}/" ]; then 28 | echo -n "[i] Deleting previous kernel sources dir linux-${kernel_version}/: " 29 | rm -rf linux-${kernel_version}/ && echo "OK" || echo "ERROR" 30 | fi 31 | echo -n "[+] Uncompressing kernel sources: " 32 | tar -xf v${kernel_version}.tar.gz && echo "OK" || echo "ERROR" 33 | 34 | if [ "${ARCH}" == "arm" -o "${ARCH}" == "arm64" ]; then 35 | echo "[+] Patching kernel sources" 36 | patch linux-${kernel_version}/arch/arm/include/asm/unified.h < ebpf_prog/arm-clang-asm-fix.patch 37 | fi 38 | 39 | echo -n "[+] Preparing kernel sources... (1-2 minutes): " 40 | echo -n "." 41 | cd linux-${kernel_version} && yes "" | make oldconfig 1>/dev/null 42 | echo -n "." 43 | make prepare 1>/dev/null 44 | echo -n "." 45 | make headers_install 1>/dev/null 46 | echo " DONE" 47 | cd ../ 48 | 49 | if [ -z $ARCH ]; then 50 | ARCH=x86 51 | fi 52 | 53 | echo "[+] Compiling eBPF modules..." 54 | cd ebpf_prog && make KERNEL_DIR=../linux-${kernel_version} KERNEL_HEADERS=../linux-${kernel_version} ARCH=${ARCH} >/dev/null 55 | # objdump -h opensnitch.o #you should see many section, number 1 should be called kprobe/tcp_v4_connect 56 | 57 | if [ ! -d modules/ ]; then 58 | mkdir modules/ 59 | fi 60 | mv opensnitch*o modules/ 61 | cd ../ 62 | llvm-strip -g ebpf_prog/modules/opensnitch*.o #remove debug info 63 | 64 | if [ -f ebpf_prog/modules/opensnitch.o ]; then 65 | echo 66 | if objdump -h ebpf_prog/modules/opensnitch.o | grep "kprobe/tcp_v4_connect"; then 67 | ls ebpf_prog/modules/*.o 68 | echo -e "\n * eBPF modules compiled. Now you can copy the *.o files to /etc/opensnitchd/ and restart the daemon\n" 69 | else 70 | echo -e "\n [WARN] opensnitch.o module not valid\n" 71 | exit 1 72 | fi 73 | else 74 | echo -e "\n [WARN] opensnitch.o module not compiled\n" 75 | exit 1 76 | fi 77 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/NEWS: -------------------------------------------------------------------------------- 1 | opensnitch (1.6.0-rc.3-1) unstable; urgency=medium 2 | 3 | From now on the eBPF modules will be installed under 4 | /usr/lib/opensnitchd/ebpf/. 5 | 6 | The daemon will look for the eBPF modules in these directories and order: 7 | - /usr/local/lib/opensnitchd/ebpf/ 8 | - /usr/lib/opensnitchd/ebpf/ 9 | 10 | Modules under /etc/opensnitchd/ will still be loaded if found, but it's 11 | deprecated and will be removed in the future. 12 | 13 | There's a new module to intercept processes execution. It may cause some 14 | rules not to match: for example if you allowed /bin/telnet, now it may be 15 | reported as /usr/bin/inteutils-telnet 16 | 17 | These cases are mostly expected. We'll keep improving it, sorry for 18 | the inconveniences. 19 | 20 | -- gustavo-iniguez-goya <gustavo.iniguez.goya@gmail.com> Wed, 19 Oct 2022 00:15:19 +0200 21 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/control: -------------------------------------------------------------------------------- 1 | Source: opensnitch 2 | Maintainer: Debian Go Packaging Team <team+pkg-go@tracker.debian.org> 3 | Uploaders: Gustavo Iniguez Goya <gooffy@gmail.com> 4 | Section: devel 5 | Testsuite: autopkgtest-pkg-go 6 | Priority: optional 7 | Build-Depends: 8 | debhelper-compat (= 11), 9 | debhelper (>= 9), 10 | dh-golang, 11 | golang-any, 12 | golang-github-vishvananda-netlink-dev, 13 | golang-github-google-gopacket-dev, 14 | golang-github-fsnotify-fsnotify-dev, 15 | golang-golang-x-net-dev, 16 | golang-google-grpc-dev, 17 | golang-goprotobuf-dev, 18 | pkg-config, 19 | libnetfilter-queue-dev, 20 | libmnl-dev 21 | Standards-Version: 4.4.0 22 | Vcs-Browser: https://salsa.debian.org/go-team/packages/opensnitch 23 | Vcs-Git: https://salsa.debian.org/go-team/packages/opensnitch.git 24 | Homepage: https://github.com/evilsocket/opensnitch 25 | Rules-Requires-Root: no 26 | XS-Go-Import-Path: github.com/evilsocket/opensnitch 27 | 28 | Package: opensnitch 29 | Section: net 30 | Architecture: any 31 | Depends: 32 | libnetfilter-queue1, libc6, libnfnetlink0 33 | Built-Using: ${misc:Built-Using} 34 | Description: GNU/Linux interactive application firewall 35 | OpenSnitch is a GNU/Linux firewall application. 36 | Whenever a program makes a connection, it'll prompt the user to allow or deny 37 | it. 38 | . 39 | The user can decide if block the outgoing connection based on properties of 40 | the connection: by port, by uid, by dst ip, by program or a combination 41 | of them. 42 | . 43 | These rules can last forever, until the app restart or just one time. 44 | . 45 | The GUI allows the user to view live outgoing connections, as well as search 46 | by process, user, host or port. 47 | . 48 | OpenSnitch can also work as a system-wide domains blocker, by using lists 49 | of domains, list of IPs or list of regular expressions. 50 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://github.com/evilsocket/opensnitch 3 | Upstream-Name: opensnitch 4 | Files-Excluded: 5 | Godeps/_workspace 6 | 7 | Files: * 8 | Copyright: 9 | 2017-2018 evilsocket 10 | 2019-2020 Gustavo Iñiguez Goia 11 | Comment: Debian packaging is licensed under the same terms as upstream 12 | License: GPL-3.0 13 | This program is free software; you can redistribute it 14 | and/or modify it under the terms of the GNU General Public 15 | License as published by the Free Software Foundation; either 16 | version 3 of the License, or (at your option) any later 17 | version. 18 | . 19 | This program is distributed in the hope that it will be 20 | useful, but WITHOUT ANY WARRANTY; without even the implied 21 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 22 | PURPOSE. See the GNU General Public License for more 23 | details. 24 | . 25 | You should have received a copy of the GNU General Public 26 | License along with this program. If not, If not, see 27 | http://www.gnu.org/licenses/. 28 | . 29 | On Debian systems, the full text of the GNU General Public 30 | License version 3 can be found in the file 31 | '/usr/share/common-licenses/GPL-3'. 32 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/gbp.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | pristine-tar = True 3 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | # auto-generated, DO NOT MODIFY. 2 | # The authoritative copy of this file lives at: 3 | # https://salsa.debian.org/go-team/ci/blob/master/config/gitlabciyml.go 4 | 5 | # TODO: publish under debian-go-team/ci 6 | image: stapelberg/ci2 7 | 8 | test_the_archive: 9 | artifacts: 10 | paths: 11 | - before-applying-commit.json 12 | - after-applying-commit.json 13 | script: 14 | # Create an overlay to discard writes to /srv/gopath/src after the build: 15 | - "rm -rf /cache/overlay/{upper,work}" 16 | - "mkdir -p /cache/overlay/{upper,work}" 17 | - "mount -t overlay overlay -o lowerdir=/srv/gopath/src,upperdir=/cache/overlay/upper,workdir=/cache/overlay/work /srv/gopath/src" 18 | - "export GOPATH=/srv/gopath" 19 | - "export GOCACHE=/cache/go" 20 | # Build the world as-is: 21 | - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > before-applying-commit.json" 22 | # Copy this package into the overlay: 23 | - "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'" 24 | - "pgt-gopath -dsc /tmp/export/*.dsc" 25 | # Rebuild the world: 26 | - "ci-build -exemptions=/var/lib/ci-build/exemptions.json > after-applying-commit.json" 27 | - "ci-diff before-applying-commit.json after-applying-commit.json" 28 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/opensnitch.init: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ### BEGIN INIT INFO 4 | # Provides: opensnitchd 5 | # Required-Start: $network $local_fs 6 | # Required-Stop: $network $local_fs 7 | # Default-Start: 2 3 4 5 8 | # Default-Stop: 0 1 6 9 | # Short-Description: opensnitchd daemon 10 | # Description: opensnitch application firewall 11 | ### END INIT INFO 12 | 13 | NAME=opensnitchd 14 | PIDDIR=/var/run/$NAME 15 | OPENSNITCHDPID=$PIDDIR/$NAME.pid 16 | 17 | # clear conflicting settings from the environment 18 | unset TMPDIR 19 | 20 | test -x /usr/bin/$NAME || exit 0 21 | 22 | . /lib/lsb/init-functions 23 | 24 | case $1 in 25 | start) 26 | log_daemon_msg "Starting opensnitch daemon" $NAME 27 | if [ ! -d /etc/$NAME/rules ]; then 28 | mkdir -p /etc/$NAME/rules &>/dev/null 29 | fi 30 | 31 | # Make sure we have our PIDDIR, even if it's on a tmpfs 32 | install -o root -g root -m 755 -d $PIDDIR 33 | 34 | if ! start-stop-daemon --start --quiet --oknodo --pidfile $OPENSNITCHDPID --background --exec /usr/bin/$NAME -- -rules-path /etc/$NAME/rules; then 35 | log_end_msg 1 36 | exit 1 37 | fi 38 | 39 | log_end_msg 0 40 | ;; 41 | stop) 42 | 43 | log_daemon_msg "Stopping $NAME daemon" $NAME 44 | 45 | start-stop-daemon --stop --quiet --signal QUIT --name $NAME 46 | # Wait a little and remove stale PID file 47 | sleep 1 48 | if [ -f $OPENSNITCHDPID ] && ! ps h `cat $OPENSNITCHDPID` > /dev/null 49 | then 50 | rm -f $OPENSNITCHDPID 51 | fi 52 | 53 | log_end_msg 0 54 | 55 | ;; 56 | reload) 57 | log_daemon_msg "Reloading $NAME" $NAME 58 | 59 | start-stop-daemon --stop --quiet --signal HUP --pidfile $OPENSNITCHDPID 60 | 61 | log_end_msg 0 62 | ;; 63 | restart|force-reload) 64 | $0 stop 65 | sleep 1 66 | $0 start 67 | ;; 68 | status) 69 | status_of_proc /usr/bin/$NAME $NAME 70 | exit $? 71 | ;; 72 | *) 73 | echo "Usage: /etc/init.d/opensnitchd {start|stop|reload|restart|force-reload|status}" 74 | exit 1 75 | ;; 76 | esac 77 | 78 | exit 0 79 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/opensnitch.install: -------------------------------------------------------------------------------- 1 | daemon/default-config.json etc/opensnitchd/ 2 | daemon/system-fw.json etc/opensnitchd/ 3 | daemon//network_aliases.json etc/opensnitchd/ 4 | ebpf_prog/opensnitch.o usr/lib/opensnitchd/ebpf/ 5 | ebpf_prog/opensnitch-dns.o usr/lib/opensnitchd/ebpf/ 6 | ebpf_prog/opensnitch-procs.o usr/lib/opensnitchd/ebpf/ 7 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/opensnitch.logrotate: -------------------------------------------------------------------------------- 1 | /var/log/opensnitchd.log { 2 | rotate 7 3 | # order of the fields is important 4 | maxsize 50M 5 | # we need this option in order to keep logging 6 | copytruncate 7 | missingok 8 | notifempty 9 | delaycompress 10 | compress 11 | create 640 root root 12 | weekly 13 | } 14 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/opensnitch.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Application firewall OpenSnitch 3 | Documentation=https://github.com/gustavo-iniguez-goya/opensnitch/wiki 4 | Wants=network.target 5 | After=network.target 6 | 7 | [Service] 8 | Type=simple 9 | PermissionsStartOnly=true 10 | ExecStartPre=/bin/mkdir -p /etc/opensnitchd/rules 11 | ExecStart=/usr/bin/opensnitchd -rules-path /etc/opensnitchd/rules 12 | Restart=always 13 | RestartSec=30 14 | TimeoutStopSec=10 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | export DH_VERBOSE = 1 3 | export DESTDIR = debian/opensnitch 4 | 5 | override_dh_installsystemd: 6 | dh_installsystemd --restart-after-upgrade 7 | 8 | execute_before_dh_auto_build: 9 | cd proto; make ../daemon/ui/protocol/ui.pb.go 10 | 11 | execute_before_dh_auto_install: 12 | mkdir -p $(DESTDIR)/usr/bin 13 | mv _build/bin/daemon $(DESTDIR)/usr/bin/opensnitchd 14 | 15 | override_dh_auto_install: 16 | dh_auto_install -- --no-source 17 | 18 | %: 19 | dh $@ --builddirectory=_build --buildsystem=golang --with=golang 20 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /utils/packaging/daemon/deb/debian/watch: -------------------------------------------------------------------------------- 1 | version=4 2 | opts=filenamemangle=s/.+\/v?(\d\S*)\.tar\.gz/opensnitch-\$1\.tar\.gz/,\ 3 | uversionmangle=s/(\d)[_\.\-\+]?(RC|rc|pre|dev|beta|alpha)[.]?(\d*)$/\$1~\$2\$3/ \ 4 | https://github.com/evilsocket/opensnitch/tags .*/v?(\d\S*)\.tar\.gz 5 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/control: -------------------------------------------------------------------------------- 1 | Source: opensnitch-ui 2 | Maintainer: Gustavo Iñiguez Goia <gooffy1@gmail.com> 3 | Uploaders: Gustavo Iniguez Goya <gooffy@gmail.com>, 4 | Priority: optional 5 | Homepage: https://github.com/evilsocket/opensnitch 6 | Build-Depends: 7 | qttools5-dev-tools, 8 | python3-setuptools, 9 | pyqt5-dev-tools, 10 | python3-grpc-tools, 11 | python3-all, 12 | debhelper (>= 7.4.3), 13 | dh-python 14 | Standards-Version: 3.9.1 15 | 16 | 17 | Package: python3-opensnitch-ui 18 | Architecture: all 19 | Section: net 20 | Depends: 21 | netbase, 22 | libqt5sql5-sqlite, 23 | python3:any, 24 | python3-six, 25 | python3-pyqt5, 26 | python3-pyqt5.qtsql, 27 | python3-pyinotify, 28 | python3-grpcio, 29 | python3-protobuf, 30 | python3-packaging, 31 | python3-slugify, 32 | python3-notify2, 33 | xdg-user-dirs, 34 | gtk-update-icon-cache 35 | Recommends: python3-pyasn 36 | Description: GNU/Linux interactive application firewall 37 | opensnitch-ui is a GUI for opensnitch written in Python. 38 | It allows the user to view live outgoing connections, as well as search 39 | for details of the intercepted connections. 40 | . 41 | The user can decide if block outgoing connections based on properties of 42 | the connection: by port, by uid, by dst ip, by program or a combination 43 | of them. 44 | . 45 | These rules can last forever, until restart the daemon or just one time. 46 | . 47 | OpenSnitch can also work as a system-wide domains blocker, by using lists 48 | of domains, list of IPs or list of regular expressions. 49 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/copyright: -------------------------------------------------------------------------------- 1 | Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ 2 | Source: https://github.com/evilsocket/opensnitch 3 | Upstream-Name: opensnitch-ui 4 | Files: * 5 | Copyright: 6 | 2017-2018 evilsocket 7 | 2019-2022 Gustavo Iñiguez Goia 8 | Comment: Debian packaging is licensed under the same terms as upstream 9 | License: GPL-3.0 10 | This program is free software; you can redistribute it 11 | and/or modify it under the terms of the GNU General Public 12 | License as published by the Free Software Foundation; either 13 | version 3 of the License, or (at your option) any later 14 | version. 15 | . 16 | This program is distributed in the hope that it will be 17 | useful, but WITHOUT ANY WARRANTY; without even the implied 18 | warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 19 | PURPOSE. See the GNU General Public License for more 20 | details. 21 | . 22 | You should have received a copy of the GNU General Public 23 | License along with this program. If not, If not, see 24 | http://www.gnu.org/licenses/. 25 | . 26 | On Debian systems, the full text of the GNU General Public 27 | License version 3 can be found in the file 28 | '/usr/share/common-licenses/GPL-3'. 29 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/postinst: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | # https://github.com/evilsocket/opensnitch/issues/647 6 | wa_grpcio_647() 7 | { 8 | badversion="1.30.2-3build6" 9 | source /etc/os-release 10 | if [ "$ID" = "linuxmint" -o "$ID" = "ubuntu" -o "$ID" = "pop" -o "$ID" = "elementary" -o "$ID" = "zorin" ]; then 11 | v=$(dpkg-query -W -f '${Version}' python3-grpcio) 12 | if [ "$v" = "$badversion" ]; then 13 | echo 14 | echo 15 | echo "@@@@@@@@@@@@@@@@@@@ WARNING @@@@@@@@@@@@@@@@@@@@" 16 | echo " invalid python3-grpcio version installed" 17 | echo "@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@" 18 | echo 19 | echo "Installed python3-grpcio package ($badversion) has a bug which makes opensnitch UI unresponsive." 20 | echo 21 | echo "Launch opensnitch-ui, and if it consumes 100% of the CPU, try this:" 22 | echo "~ $ sudo apt install python3-pip" 23 | echo "~ $ pip3 install grpcio==1.41.0" 24 | echo "~ $ pip3 install protobuf==3.20.0" 25 | echo 26 | echo "More information:" 27 | echo " - https://bugs.launchpad.net/ubuntu/+source/grpc/+bug/1971114" 28 | echo " - " 29 | echo " - https://github.com/evilsocket/opensnitch/issues/647" 30 | echo 31 | echo 32 | fi 33 | fi 34 | } 35 | 36 | autostart_by_default() 37 | { 38 | deskfile=/etc/xdg/autostart/opensnitch_ui.desktop 39 | if [ -d /etc/xdg/autostart -a ! -h $deskfile -a ! -f $deskfile ]; then 40 | ln -s /usr/share/applications/opensnitch_ui.desktop /etc/xdg/autostart/ 41 | fi 42 | } 43 | 44 | autostart_by_default 45 | 46 | if command -v gtk-update-icon-cache >/dev/null && test -f /usr/share/icons/hicolor/index.theme ; then 47 | gtk-update-icon-cache --quiet /usr/share/icons/hicolor/ 48 | fi 49 | 50 | wa_grpcio_647 51 | 52 | #DEBHELPER# 53 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/postrm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | 4 | purge_files() 5 | { 6 | for i in $(ls /home) 7 | do 8 | path=/home/$i/.config/ 9 | if [ -h $path/autostart/opensnitch_ui.desktop -o -f $path/autostart/opensnitch_ui.desktop ];then 10 | rm -f $path/autostart/opensnitch_ui.desktop 11 | fi 12 | if [ -d $path/opensnitch/ ]; then 13 | rm -rf $path/opensnitch/ 14 | fi 15 | done 16 | 17 | deskfile=/etc/xdg/autostart/opensnitch_ui.desktop 18 | if [ -h $deskfile -o -f $deskfile ]; then 19 | rm -f $deskfile 20 | fi 21 | } 22 | 23 | pkill -15 opensnitch-ui || true 24 | 25 | case "$1" in 26 | purge) 27 | purge_files 28 | ;; 29 | remove) 30 | purge_files 31 | ;; 32 | esac 33 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | 3 | # This file was automatically generated by stdeb 0.9.0 at 4 | # Thu, 06 Feb 2020 00:20:02 +0100 5 | 6 | %: 7 | dh $@ --with python3 --buildsystem=python_distutils 8 | 9 | 10 | override_dh_auto_clean: 11 | rm -f opensnitch/resources_rc.py 12 | rm -rf opensnitch/i18n/ 13 | python3 setup.py clean -a 14 | find . -name \*.pyc -exec rm {} \; 15 | 16 | override_dh_auto_build: 17 | python3 setup.py build --force 18 | 19 | override_dh_auto_install: 20 | cd i18n; make 21 | pyrcc5 -o opensnitch/resources_rc.py opensnitch/res/resources.qrc 22 | find opensnitch/proto/ -name 'ui_pb2_grpc.py' -exec sed -i 's/^import ui_pb2/from . import ui_pb2/' {} \; 23 | python3 setup.py install --force --root=debian/python3-opensnitch-ui --no-compile -O0 --install-layout=deb 24 | 25 | override_dh_python2: 26 | dh_python2 --no-guessing-versions 27 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (quilt) 2 | -------------------------------------------------------------------------------- /utils/packaging/ui/deb/debian/source/options: -------------------------------------------------------------------------------- 1 | extend-diff-ignore="\.egg-info$" -------------------------------------------------------------------------------- /utils/scripts/debug-ebpf-maps.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # OpenSnitch - 2023 4 | # https://github.com/evilsocket/opensnitch 5 | # 6 | # Usage: bash debug-ebpf-maps.sh tcp (or tcpv6, udp, udpv6) 7 | # 8 | 9 | function print_map_proto 10 | { 11 | case "$1" in 12 | 12001) 13 | echo "------------------------------ TCP ------------------------------" 14 | ;; 15 | 12002) 16 | echo "------------------------------ TCPv6 ------------------------------" 17 | ;; 18 | 12003) 19 | echo "------------------------------ UDP ------------------------------" 20 | ;; 21 | 12004) 22 | echo "------------------------------ UDPv6 ------------------------------" 23 | ;; 24 | esac 25 | } 26 | 27 | function dump_map 28 | { 29 | echo 30 | print_map_proto $mid 31 | bpftool map dump id $1 |awk ' 32 | BEGIN { total=0; } 33 | { 34 | split($0, line); 35 | if (line[1] == "key:"){ 36 | is_key=1; 37 | total++; 38 | } else if (is_key == 1){ 39 | sport=strtonum("0x" line[2] line[1]); 40 | dport=strtonum("0x" line[7] line[8]); 41 | printf("%d:%d.%d.%d.%d -> %d.%d.%d.%d:%d\n", 42 | sport, 43 | strtonum("0x" line[3]), 44 | strtonum("0x" line[4]), 45 | strtonum("0x" line[5]), 46 | strtonum("0x" line[6]), 47 | strtonum("0x" line[9]), 48 | strtonum("0x" line[10]), 49 | strtonum("0x" line[11]), 50 | strtonum("0x" line[12]), 51 | dport); 52 | is_key=0; 53 | } 54 | } 55 | END { printf("Total: %d\n", total); }' 56 | print_map_proto $mid 57 | } 58 | 59 | if [ -z $1 ]; then 60 | echo 61 | echo " Usage: bash debug-ebpf-maps.sh <proto> (tcp, tcpv6, udp or udpv6)" 62 | echo 63 | exit 64 | fi 65 | if ! command -v bpftool; then 66 | echo 67 | echo " [error] bpftool not found. Install it." 68 | echo 69 | exit 70 | fi 71 | 72 | mid=0 73 | case "$1" in 74 | tcp) 75 | mid=$(bpftool map list | grep -B 1 12001 | grep hash | cut -d: -f1) 76 | ;; 77 | tcpv6) 78 | mid=$(bpftool map list | grep -B 1 12002 | grep hash | cut -d: -f1) 79 | ;; 80 | udp) 81 | mid=$(bpftool map list | grep -B 1 12003 | grep hash | cut -d: -f1) 82 | ;; 83 | udpv6) 84 | mid=$(bpftool map list | grep -B 1 12004 | grep hash | cut -d: -f1) 85 | ;; 86 | esac 87 | if [ $mid -eq 0 ]; then 88 | echo 89 | echo " [error] Invalid protocol ($1)" 90 | echo 91 | exit 92 | fi 93 | 94 | dump_map $mid 95 | -------------------------------------------------------------------------------- /utils/scripts/ipasn_db_sync.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Synchronize ipasn and asnames data for use with OpenSnitch 4 | # 5 | # Author: Self Denial <selfdenial at pm dot me> 6 | # 7 | # This script downloads pre-processed asn data from 8 | # https://github.com/lainedfles/opensnitch-asn-data 9 | # Wget is required. 10 | # 11 | # Example crontab: 12 | # 13 | # Poll every 7 days 14 | # 0 0 */7 * * /home/user/.config/opensnitch/ipasn_db_sync.sh 2>&1 | logger -t ipasn_db_sync.sh 15 | 16 | # Vars 17 | OPENSNITCH_CONF_PATH=~/.config/opensnitch 18 | SOURCE_REPO="https://github.com/lainedfles/opensnitch-asn-data" 19 | IPASN_FILE="ipasn_db.dat.gz" 20 | ASNAMES_FILE="asnames.json" 21 | 22 | # Ensure wget are available 23 | if ! command -v "wget" &>/dev/null; then 24 | echo "wget not found! Please ensure that wget is in your PATH." 25 | exit 1 26 | fi 27 | 28 | # Ensure destination exists 29 | if [ ! -e "$OPENSNITCH_CONF_PATH" ]; then 30 | mkdir -pv "$OPENSNITCH_CONF_PATH" || exit 1 31 | fi 32 | cd "$OPENSNITCH_CONF_PATH" || exit 1 33 | 34 | # Update asnames 35 | echo "******** Updating $ASNAMES_FILE... ********" 36 | # Create backup 37 | [ -f "$ASNAMES_FILE" ] && mv -vf "$ASNAMES_FILE" "$ASNAMES_FILE.last" 38 | if wget --no-verbose --output-document="$ASNAMES_FILE" "${SOURCE_REPO}/releases/latest/download/$ASNAMES_FILE"; then 39 | echo "Updated asnames data" 40 | else 41 | echo "Failed to update asnames data, restoring backup" 42 | # Restore backup upon failure 43 | mv -vf "$ASNAMES_FILE.last" "$ASNAMES_FILE" 44 | fi 45 | 46 | # Update ipasn db 47 | echo "******** Updating $IPASN_FILE... ********" 48 | # Create backup 49 | [ -f "$IPASN_FILE" ] && mv -vf "$IPASN_FILE" "$IPASN_FILE.last" 50 | if wget --no-verbose --output-document="$IPASN_FILE" "${SOURCE_REPO}/releases/latest/download/$IPASN_FILE"; then 51 | echo "Downloaded ipasn data" 52 | else 53 | echo "Failed to download ipasn data, restoring backup" 54 | mv -vf "$IPASN_FILE.last" "$IPASN_FILE" 55 | fi 56 | -------------------------------------------------------------------------------- /utils/scripts/ipasn_db_update.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Update ipasn and asnames data for use with OpenSnitch 4 | # 5 | # Author: Self Denial <selfdenial at pm dot me> 6 | # 7 | # This script requires the pyasn module from: https://github.com/hadiasghari/pyasn 8 | # Specifically, the pyasn-utils pyasn_util_asnames.py, pyasn_util_download.py, 9 | # and pyasn_util_convert.py. These must be available with the PATH variable. 10 | # 11 | # Example crontab: 12 | # 13 | # Update every 14 days 14 | # 0 0 */14 * * /home/user/.config/opensnitch/ipasn_db_update.sh 2>&1 | logger -t ipasn_db_update.sh 15 | 16 | # Vars 17 | OPENSNITCH_CONF_PATH=~/.config/opensnitch 18 | IPASN_FILE="${OPENSNITCH_CONF_PATH}/ipasn_db.dat" 19 | ASNAMES_FILE="${OPENSNITCH_CONF_PATH}/asnames.json" 20 | RIBDATA_FILE="${OPENSNITCH_CONF_PATH}/rib-data.bz2" 21 | 22 | # Ensure pyasn-utils are available 23 | for PYASN_UTIL in pyasn_util_{asnames,convert,download}.py; do 24 | if ! command -v "$PYASN_UTIL" &>/dev/null; then 25 | echo "$PYASN_UTIL not found! Please ensure that the pyasn-utils are in your PATH." 26 | exit 1 27 | fi 28 | done 29 | 30 | # Ensure destination exists 31 | if [ ! -e "$OPENSNITCH_CONF_PATH" ]; then 32 | mkdir -pv "$OPENSNITCH_CONF_PATH" || exit 1 33 | fi 34 | 35 | # Update asnames 36 | echo "******** Updating ${ASNAMES_FILE##*/}... ********" 37 | # Create backup 38 | [ -f "$ASNAMES_FILE" ] && mv -vf "$ASNAMES_FILE" "$ASNAMES_FILE.last" 39 | if pyasn_util_asnames.py -o "$ASNAMES_FILE"; then 40 | echo "Updated asnames data" 41 | else 42 | echo "Failed to update asnames data, restoring backup" 43 | # Restore backup upon failure 44 | mv -vf "$ASNAMES_FILE.last" "$ASNAMES_FILE" 45 | fi 46 | 47 | # Update ipasn db 48 | echo "******** Updating ${IPASN_FILE##*/}... ********" 49 | # Create backup 50 | [ -f "${IPASN_FILE}.gz" ] && mv -vf "${IPASN_FILE}.gz" "${IPASN_FILE}.gz.last" 51 | # Clean up rib data if needed 52 | [ -e "$RIBDATA_FILE" ] && rm -vf "$RIBDATA_FILE" 53 | # Pull both ipv4 & ipv6 54 | # The resulting rib files typically include a date string in the name 55 | # use --filename to identify 56 | if pyasn_util_download.py --latestv46 --filename "$RIBDATA_FILE"; then 57 | echo "Downloaded ipasn data" 58 | if pyasn_util_convert.py --single "$RIBDATA_FILE" "$IPASN_FILE" --compress --no-progress; then 59 | echo "Converted ipasn data" 60 | else 61 | echo "Failed to convert ipasn data, restoring backup" 62 | mv -vf "${IPASN_FILE}.gz.last" "${IPASN_FILE}.gz" 63 | fi 64 | else 65 | echo "Failed to download ipasn data, restoring backup" 66 | mv -vf "${IPASN_FILE}.gz.last" "${IPASN_FILE}.gz" 67 | fi 68 | -------------------------------------------------------------------------------- /utils/scripts/restart-opensnitch-onsleep.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # opensnitch - 2022-2023 3 | # 4 | # Due to a bug in gobpf, when coming back from suspend state, ebpf stops working. 5 | # The temporal solution is to stop/start the daemon on suspend. 6 | # 7 | # Copy it to /lib/systemd/system-sleep/ with any name and exec permissions. 8 | # 9 | if [ "$1" == "pre" ]; then 10 | service opensnitchd stop 11 | elif [ "$1" == "post" ]; then 12 | service opensnitchd stop 13 | service opensnitchd start 14 | fi 15 | --------------------------------------------------------------------------------