Sailfish OS // Network Analysis // v1.1

SailShark

Wireshark, ported native to Sailfish OS

March 2026 Wireshark 3.6.24 Qt 5.6.3 / Silica QML

Motivation

Sailfish OS is one of the last truly independent mobile operating systems — a Linux-based platform with a distinctive gesture-driven UI, strong privacy defaults, and a small but dedicated community of developers and enthusiasts. Despite running a full Linux stack underneath, Sailfish has historically lacked one tool that network engineers and security researchers consider essential: a packet capture and analysis tool with a usable interface.

tshark and tcpdump can be installed and run from the terminal, but doing so on a phone is cumbersome. The full Wireshark desktop application is not portable to Sailfish without significant work — it targets Qt Widgets, assumes a large screen, and pulls in dependencies that either do not exist in the Sailfish SDK or conflict with its Qt 5.6.3 base.

SailShark closes that gap: a Sailfish-native network analyzer with a Silica QML interface designed for portrait phone use, backed by the proven dissection engine of Wireshark 3.6.x.


What's in v1.1

🦈
Live Capture
Real-time packet capture via a poll-based pcap architecture that bypasses tshark's stdout buffering entirely. New packets only — no re-dissecting the full file each cycle.
🎨
Protocol Colours
Each packet row is colour-coded by protocol — TCP, UDP, DNS, HTTP, TLS, SSH, ICMP, ARP and more.
📡
Interface Picker
A dedicated picker page lists all available interfaces from tshark -D. One tap to select.
🔍
Capture Filters
BPF capture filter expressions passed directly to tshark. Filter by host, port, protocol or any valid libpcap expression.
🌳
Protocol Tree
Full tshark verbose dissection tree per packet, fetched on demand via tshark -V, cached for instant revisits, collapsible section in the detail page.
🔬
Hex Dump
Horizontally scrollable monospace hex dump fetched on demand and cached. tshark's inline ASCII column is preserved — no separate ASCII panel needed.
🔀
Follow TCP Stream
Reassemble and view the full TCP conversation for any packet via tshark -z follow,tcp,ascii,N. Accessible from the pulley menu on TCP/TLS/HTTP/SSH packets.
💾
Save Capture
Save to ~/Documents/SailShark/ as a timestamped .pcapng. Directory is created automatically if missing.
📱
Cover Actions
Start and stop capture from the Sailfish home screen cover. Packet count visible without bringing the app to foreground.
ℹ️
About Page
In-app about page with developer info, links to Codeberg, Sailfish OBS, and sec1.dk, and a built-with summary.
🔒
Minimal Privileges
Only dumpcap runs with elevated capabilities (cap_net_raw). The UI process runs as the normal user.
5000 Packet Cap
The live packet list is capped at 5000 entries, dropping the oldest on overflow. Keeps the UI responsive during long captures.

Packet Detail — Screenshot

Screenshot of SailShark packet detail view showing Protocol Tree, Hex Dump sections

// PacketDetailPage — Protocol Tree + Hex Dump // SailfishEmul


Architecture

SailShark separates the capture engine from the UI entirely, a clean match for the Sailfish application model:

Silica QML
harbour-wireshark.qml
CaptureListPage
PacketDetailPage
TcpStreamPage
InterfacePickerPage
AboutPage · CoverPage
CaptureEngine
QObject / QProcess
Poll timer (1s)
Hex + tree + stream cache
Qt signals
tshark + dumpcap
-w pcap writer
-r -Y frame.number>N reader
-V proto tree
-z follow,tcp,ascii
cap_net_raw

The capture pipeline uses a two-process poll architecture to avoid tshark's stdout buffering problem:

Qt 5.6 QML Signal Quirk

In Qt 5.6's QML engine, signal parameters in a Connections block are not automatically bound by parameter name. Code like onPacketReceived: { pkt.protocol } raises ReferenceError: pkt is not defined. The fix is positional access via the arguments array:

onPacketReceived: {
    var pkt = arguments[0]   // Qt 5.6: names not in scope
    globalPacketModel.append({ ... })
}

This affected every signal with parameters in the project and was the root cause of packets not appearing in the UI despite the C++ engine working correctly.

mapplauncherd / PIE

Sailfish launches apps via mapplauncherd, which dlopen()s the binary into a pre-forked booster process. This requires the binary to be a position-independent executable. Without -fPIE -pie -rdynamic in the cmake build, the app launches fine from terminal but fails with "cannot dynamically load executable" from the app grid. Added to CMakeLists.txt:

set(CMAKE_POSITION_INDEPENDENT_CODE ON)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pie -rdynamic")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIE")

Technical Challenges

ChallengeSolution
Qt version mismatch Sailfish ships Qt 5.6.3. QOverload<> arrived in Qt 5.7 — all overloaded signal connections rewritten using static_cast<> disambiguation.
tshark stdout buffering tshark buffers text output aggressively. Solved by switching to a pcap writer + poll reader architecture — the writer outputs binary and a fresh reader is spawned every second.
Poll cost at high packet counts The reader was re-dissecting the entire pcap every second and skipping already-seen packets in C++. Fixed by passing -Y "frame.number > N" so tshark only outputs new packets.
QML signal parameter scope Qt 5.6 Connections blocks do not bind signal parameter names. All handlers use arguments[0], arguments[1] positional access.
Hex dump timing race Component.onCompleted fires during the page push animation before Connections is active. Moved fetch trigger to onStatusChanged with PageStatus.Active.
Proto tree stdout race readAllStandardOutput() in the finished slot races against OS pipe flushing. Fixed by accumulating into a QByteArray buffer via readyReadStandardOutput, draining the remainder in finished.
Qt 5.6 Label segfault on large text The Qt 5.6 text layout engine crashes when a Label with wrapMode: Text.NoWrap receives very large strings (proto tree output can exceed 3000+ chars). Fixed by truncating to 8000 chars in C++ and switching to Text.WrapAtWordBoundaryOrAnywhere.
mapplauncherd PIE requirement Sailfish's booster dlopen()s the app binary — requires PIE. Binary compiled as EXEC runs from terminal but fails from the app grid. Fixed with -fPIE -pie -rdynamic CMake flags.
Post-install user detection loginctl list-sessions filtered on $4 == "seat0" identifies the active graphical session user regardless of username or other logged-in accounts.
homeDir() wrong under booster QStandardPaths::HomeLocation resolves via getpwuid() which returned the wrong user under mapplauncherd. Fixed by reading the HOME environment variable first, which the invoker sets correctly.
No desktop UI on phones Set -DBUILD_wireshark=OFF. Built only tshark and dumpcap, then wrote a new Silica/QML frontend from scratch.
MOC not running on QObject CMAKE_AUTOMOC missing from sfos-ui CMakeLists.txt caused linker errors. Fixed with set(CMAKE_AUTOMOC ON).
Unicode in C++ source The OBS GCC rejects multibyte UTF-8 characters (e.g. ) as invalid identifiers in source files. All non-ASCII literals replaced with plain ASCII equivalents.

Source File Structure

The RPM spec coordinates two independent cmake builds and twelve source files:

wireshark-3.6.24/
  build/ ← tshark + libwireshark cmake build
  sfos-ui/
    main.cpp ← SailfishApp entry point
    captureengine.h/.cpp ← QProcess wrapper, caches, all tshark calls
    qml/
      harbour-wireshark.qml ← ApplicationWindow + globalPacketModel (5k cap)
      CaptureListPage.qml ← Packet list + pulley menu + save notification
      PacketDetailPage.qml ← Summary + proto tree + hex dump
      TcpStreamPage.qml ← Follow TCP stream reassembly view
      InterfacePickerPage.qml ← Interface selector
      AboutPage.qml ← Developer info + links
      CoverPage.qml ← Sailfish home screen cover
    CMakeLists.txt ← Generated by spec heredoc
    build/ ← sfos-ui cmake build output

Installation

Prerequisites

Sailfish OS 2.0 or later. Built for armv7hl, aarch64, and i486 (emulator). Developer mode and root access are required for packet capture.

Installing

Add my repository at build.sailfishos.org/nieldk for your architecture, and install using pkcon:

devel-su pkcon refresh
devel-su pkcon install sailshark

The %post scriptlet sets the required capabilities on dumpcap and creates ~/Documents/SailShark/ for the active session user:

setcap cap_net_raw,cap_net_admin=eip /usr/bin/dumpcap
If setcap is unavailable on your device, dumpcap falls back to setuid-root. Either method allows capture without running the full app as root. The Documents/SailShark directory is also re-created automatically on first save if deleted.

Enabling Developer Mode

Enable Developer Mode under Settings › System › Developer Tools. This provides SSH access and the devel-su command required for installation.


Using SailShark

Starting a Capture

  1. Launch SailShark from the app grid.
  2. Pull down from the top of the screen to open the Pulley Menu.
  3. Tap Select Interface… — a full list of available interfaces is shown. Tap one to select.
  4. Optionally enter a BPF capture filter expression in the filter field.
  5. Tap Start Capture. A status banner shows the active interface and running packet count.

Reading the Packet List

Each row shows a colour-coded protocol stripe on the left edge:

TCP UDP DNS TLS/SSL SSH HTTP ICMP ARP

The list is capped at 5000 entries — oldest packets are dropped on overflow to keep the UI responsive. Tap any row to open the detail page.

Packet Detail Page

Follow TCP Stream

On any TCP, TLS, HTTP, or SSH packet, pull down the packet detail page to reveal Follow TCP Stream. This runs tshark -z follow,tcp,ascii,N against the pcap and displays the reassembled conversation in a scrollable monospace view. The stream index is captured automatically from the tcp.stream field during live capture.

Saving a Capture

Pull down on the main list and tap Save Capture. The pcap is saved to:

~/Documents/SailShark/capture-YYYYMMDD-HHMMSS.pcapng

The directory is created automatically if it does not exist. The file can be transferred to a desktop and opened directly in Wireshark.

Stopping a Capture

Pull down and tap Stop Capture, or press the cover action button on the home screen. The packet list, pcap, hex dumps, and protocol trees remain available for browsing after stopping.

Capture Filters

BPF expressions are passed directly to tshark as -f filters:

tcp port 443          # HTTPS only
host 192.168.1.1      # Traffic to/from a specific host
not port 22           # Exclude SSH
udp and port 53       # DNS queries only
Capture filters are applied by the kernel via libpcap before packets reach userspace. They cannot be changed mid-capture — stop and restart with the new filter.

Interface Reference

InterfaceDescription
wlan0Wi-Fi — most common for general network analysis
rmnet0 / rmnet_data0Mobile data (varies by device and modem driver)
loLoopback — useful for debugging local app traffic
anyCapture on all interfaces simultaneously
usb0USB tethering or RNDIS interface
eth0Emulator virtual ethernet interface

The full list of interfaces available on your device is shown in the interface picker at launch, populated by tshark -D.


Known Limitations


Author

Built by Niel Nielsen (NielDK) — Farum, Denmark.
High-signal tech pragmatist focused on logic, efficiency, and impactful open-source contributions.

sec1.dk  ·  codeberg.org/nieldk  ·  Sailfish OBS