How witr Uses Systemd Socket Activation to Resolve Ports
witr determines port ownership by walking process ancestry to PID 1, then querying systemd over D-Bus for unit information when PID 1 is systemd, enabling resolution of socket-activated ports without inspecting raw file descriptors.
The witr open-source tool solves a common Linux debugging problem: identifying what service owns a listening port. When systemd uses socket activation, the listening socket is created by PID 1, making traditional lsof or netstat output misleading. This article explains how witr integrates systemd socket activation for port resolution by mapping processes back to their systemd units through cgroup and D-Bus inspection.
How Socket Activation Complicates Port Resolution
Systemd's socket activation allows services to be started on demand when a connection arrives. The socket is created early by PID 1, then handed to the actual service when it starts. This means ss -tlnp or similar tools show PID 1 as the owner, not the real service.
witr handles this by detecting when PID 1 is systemd, then querying systemd's own bookkeeping to find the true unit responsible for the port.
The Systemd Detection Pipeline in witr
The port resolution logic lives in internal/source/systemd_linux.go. The detectSystemd function implements a seven-step workflow:
| Step | Action | Source Location |
|---|---|---|
| 1 | Verify systemd is the init system (IsSystemdRunning) |
systemd_linux.go:L22-L27 |
| 2 | Walk process ancestry and confirm PID 1 presence | systemd_linux.go:L36-L45 |
| 3 | Extract unit name from cgroup (getUnitNameFromCgroup) |
systemd_linux.go:L47-L52 |
| 4 | Open D-Bus connection with 2-second timeout | systemd_linux.go:L52-L58 |
| 5 | Query unit properties via D-Bus | systemd_linux.go:L81-L88 |
| 6 | Check for companion timer unit if service | systemd_linux.go:L90-L98 |
| 7 | Return populated model.Source with unit metadata |
detectSystemd return value |
Each step validates preconditions before proceeding, ensuring witr only attempts D-Bus queries when systemd socket activation is actually in play.
Verifying Systemd as the Init System
Before any D-Bus communication, witr confirms systemd is running. The IsSystemdRunning function uses the canonical test from libsystemd's sd_booted():
// internal/source/systemd_linux.go
func IsSystemdRunning() bool {
// /run/systemd/system is created by systemd when it starts
_, err := os.Stat("/run/systemd/system")
return err == nil
}
This check prevents false positives on non-systemd systems like Alpine Linux or containers using other init systems.
Walking Process Ancestry and Validating PID 1
witr validates that the target process descends from PID 1, a prerequisite for socket activation. The ancestry walk in systemd_linux.go:L36-L45 aborts the systemd path if PID 1 isn't found in the chain.
This optimization avoids expensive D-Bus calls when the process clearly wasn't started through systemd's mechanisms.
Resolving Units from Cgroups
Once PID 1 is confirmed, witr extracts the systemd unit name from the process's cgroup path:
// internal/source/systemd_linux.go
func getUnitNameFromCgroup(pid int) (string, error) {
// Parses /proc/<pid>/cgroup to find systemd controller entry
// Returns unit name like "nginx.service" or "myapp.socket"
}
The cgroup filesystem exposes systemd's unit hierarchy, allowing witr to determine which unit owns the process without querying D-Bus.
Querying Unit Properties via D-Bus
With the unit name identified, witr enriches the model.Source record through D-Bus:
// Connection with timeout respecting context
conn, err := sd.NewSystemConnectionContext(ctx)
if err != nil {
return nil, fmt.Errorf("dbus connection: %w", err)
}
defer conn.Close()
// Fetch all unit properties
props, err := conn.GetUnitPropertiesContext(ctx, unitName)
From the D-Bus response, witr extracts:
- Description — human-readable service description
- FragmentPath — location of the unit file (e.g.,
/etc/systemd/system/myservice.service) - NRestarts — restart count for failure analysis
Detecting Companion Timer Units
For service units, witr performs a secondary query to find associated timer units:
// internal/source/systemd_linux.go:L90-L98
if strings.HasSuffix(unitName, ".service") {
timerUnit := strings.TrimSuffix(unitName, ".service") + ".timer"
schedule, err := timerSchedule(ctx, conn, timerUnit)
if err == nil {
src.TimerSchedule = schedule
}
}
This captures calendar or monotonic timer specifications, useful for understanding when socket-activated services might next trigger.
The Complete Resolution Flow
Here's how witr resolves a socket-activated port in practice:
// Example: Resolve the owner of port 8080
port := 8080
src, err := witr.ResolvePort(port) // internal logic walks the process tree
if err != nil {
log.Fatal(err)
}
if src.Type == model.SourceSystemd {
fmt.Printf("Port %d is owned by systemd unit %s (%s)\n",
port, src.Name, src.Description)
}
The SourceSystemd type in pkg/model/source.go signals that this port was resolved through systemd's socket-activation bookkeeping rather than direct process ownership.
Minimal Implementation Pattern
The core pattern witr uses can be adapted for other tools:
// Minimal snippet that mirrors witr's systemd detection
if source.IsSystemdRunning() && hasPID1(ancestry) {
unit := source.getUnitNameFromCgroup(pid)
conn, _ := sd.NewSystemConnectionContext(ctx)
props, _ := conn.GetUnitPropertiesContext(ctx, unit)
fmt.Println("Systemd unit:", unit)
fmt.Println("Description :", stringProp(props, "Description"))
}
Note the defensive programming: each step validates preconditions before the next, with appropriate error handling at each layer.
Key Source Files and Their Roles
| File | Purpose |
|---|---|
internal/source/systemd_linux.go |
Core detection, D-Bus queries, model.Source enrichment |
pkg/model/source.go |
Defines SourceSystemd constant and data structures |
internal/source/detect.go |
Orchestrates systemd, container, SSH, and other detectors |
README.md |
Documents the "Port → Container fallback" feature mentioning socket activation |
The architecture cleanly separates detection concerns: detect.go selects the appropriate resolver, while systemd_linux.go handles systemd-specific logic in isolation.
Summary
- witr detects systemd socket activation by walking process ancestry to PID 1 and verifying
/run/systemd/systemexists - Unit resolution uses cgroups, not socket file descriptor inspection, making it robust across kernel versions
- D-Bus queries enrich port metadata with human-readable descriptions, unit file locations, and timer schedules
- Two-second timeouts prevent hangs on misconfigured or overloaded systems
- The
SourceSystemdtype allows upstream code to distinguish socket-activated ports from directly-owned ports
Frequently Asked Questions
How does witr distinguish socket activation from regular systemd services?
witr doesn't fundamentally distinguish them—it treats any process owned by a systemd unit as potentially socket-activated. The key indicator is PID 1 in the ancestry combined with a valid unit name from cgroups. Whether the service was started by socket activation or manually is transparent to witr's resolution logic; both cases resolve to the same unit information.
Why check /run/systemd/system instead of reading /proc/1/comm?
The /run/systemd/system check mirrors sd_booted() from libsystemd, the official systemd API. This is more reliable than checking process names because: PID 1 might be renamed, container environments may show misleading names, and the presence of systemd files confirms the system was booted with systemd—not just that a systemd binary exists.
What happens if D-Bus is unavailable or slow?
witr's D-Bus connection in systemd_linux.go:L52-L58 respects a 2-second timeout defined by dbusTimeout. If the connection fails or queries timeout, the error propagates up and witr falls back to other detection methods (containers, SSH, etc.) or returns an error. This prevents indefinite hangs on systems with D-Bus issues.
Can witr resolve ports for systemd user services, not just system services?
The current implementation in internal/source/systemd_linux.go uses sd.NewSystemConnectionContext, which connects to the system bus. User services on the session bus would require NewUserConnectionContext and additional logic to determine which user session owns the process. This is not currently implemented in witr.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →