A fast, cross-platform network port monitoring library and CLI tool written in Zig. PortWatchZ tracks listening ports and established connections, detects changes, and can send alerts via webhooks.
- Multi-platform support: Linux (via
/proc/net), macOS, FreeBSD, OpenBSD, NetBSD, Windows (IP Helper API) - Process information: Maps ports to process IDs, executables, command lines, and user/group IDs
- Binary integrity: BLAKE3 hashing of executable files to detect changes
- Flexible filtering: Include/exclude established connections, ephemeral ports, IPv4/IPv6
- Multiple output formats: Human-readable text, JSON, ECS (Elastic Common Schema)
- Webhook integration: Send alerts with HMAC authentication and mTLS support
- Baseline comparison: Track changes against saved snapshots
- Continuous monitoring: Watch mode for real-time detection
- Native OS APIs: Platform-optimized implementations for maximum performance
git clone https://github.com/zombocoder/portwatchz.git
cd portwatchz
zig build -Doptimize=ReleaseFastThe compiled binary will be available at zig-out/bin/portwatchz.
Add to your build.zig.zon:
.dependencies = .{
.portwatchz = .{
.url = "https://github.com/zombocoder/portwatchz/archive/v0.1.0.tar.gz",
.hash = "12345...", // Replace with actual hash
},
},
-
Create a configuration file (optional but recommended):
portwatchz init-config
-
Create a baseline of current network ports:
portwatchz init
-
Check for changes against the baseline:
portwatchz check
-
Monitor continuously for changes:
portwatchz watch --interval 5
const std = @import("std");
const portwatchz = @import("portwatchz");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Take a snapshot of current ports
const options = portwatchz.Options{
.process_info = true,
.hash_binaries = true,
};
var snapshot = try portwatchz.snapshot(allocator, options);
defer snapshot.deinit(allocator);
std.log.info("Found {} listening ports", .{snapshot.records.len});
// Save as baseline
try portwatchz.saveBaseline("baseline_net.db", &snapshot);
}portwatchz init- Create a baseline of current network portsportwatchz check- Compare current ports against baselineportwatchz watch- Continuously monitor for changesportwatchz init-config- Create default configuration fileportwatchz help- Show help information
--config <PATH>- Configuration file path (default:portwatchz.conf)--baseline <PATH>- Baseline file path (default:baseline_net.db)--output <FORMAT>- Output format:text,json,ecs(default:text)
--include-established- Include established TCP connections (not just listening ports)--include-ephemeral- Include ports in ephemeral ranges--no-process-info- Skip gathering process information--no-hash-binaries- Skip computing executable hashes--only-ipv4- Only scan IPv4 addresses--only-ipv6- Only scan IPv6 addresses--filter <PATTERN>- Add port filter (can be used multiple times)
--interval <SECONDS>- Watch interval in seconds (default: 2)
--webhook-url <URL>- Webhook endpoint URL--webhook-hmac-key <KEY>- HMAC key for webhook authentication--webhook-mtls-cert <PATH>- mTLS certificate file path--webhook-mtls-key <PATH>- mTLS private key file path--webhook-timeout <MS>- Webhook timeout in milliseconds (default: 30000)--webhook-retries <N>- Number of retry attempts (default: 3)--webhook-batch-size <N>- Events per webhook batch (default: 100)
Filters allow you to focus on specific ports or addresses:
:22- Port 22 (any address)127.0.0.1:- Any port on localhosttcp:8080- Port 8080 TCP onlyudp:53- Port 53 UDP only
# Create default configuration file
portwatchz init-config
# Create baseline including established connections
portwatchz init --include-established
# Check only SSH and HTTPS ports
portwatchz check --filter :22 --filter :443
# Monitor with JSON output and webhook alerts
portwatchz watch --output json --webhook-url https://example.com/alerts
# Monitor only listening ports on specific interface
portwatchz watch --filter 192.168.1.100: --interval 10
# Use custom configuration file
portwatchz watch --config custom.confPortWatchZ supports configuration files to set default options and simplify command-line usage.
Configuration files use a simple key=value format:
# PortWatchZ Configuration File
# Lines starting with # are comments
baseline_path=baseline_net.db
watch_interval=2
output_format=text
# Monitoring options
include_established=false
include_ephemeral=false
process_info=true
hash_binaries=true
only_ipv4=false
only_ipv6=false
# Port filters (can specify multiple)
port_filter=:22
port_filter=:443
port_filter=tcp:8080
# Webhook configuration
webhook_url=https://example.com/alerts
webhook_hmac_key=your-secret-key
webhook_timeout=30000
webhook_retries=3
webhook_batch_size=100
| Option | Type | Default | Description |
|---|---|---|---|
baseline_path |
string | baseline_net.db |
Path to baseline file |
watch_interval |
integer | 2 |
Watch interval in seconds |
output_format |
string | text |
Output format (text, json, ecs) |
include_established |
boolean | false |
Include established connections |
include_ephemeral |
boolean | false |
Include ephemeral ports |
process_info |
boolean | true |
Gather process information |
hash_binaries |
boolean | true |
Compute executable hashes |
only_ipv4 |
boolean | false |
Only scan IPv4 addresses |
only_ipv6 |
boolean | false |
Only scan IPv6 addresses |
port_filter |
string | - | Port filter pattern (repeatable) |
webhook_url |
string | - | Webhook endpoint URL |
webhook_hmac_key |
string | - | HMAC key for authentication |
webhook_mtls_cert |
string | - | mTLS certificate file |
webhook_mtls_key |
string | - | mTLS private key file |
webhook_timeout |
integer | 30000 |
Webhook timeout (milliseconds) |
webhook_retries |
integer | 3 |
Number of retry attempts |
webhook_batch_size |
integer | 100 |
Events per webhook batch |
-
Create a default configuration:
portwatchz init-config
-
Edit the configuration in
portwatchz.confas needed -
Use with commands:
# Uses portwatchz.conf automatically if present portwatchz watch # Or specify a custom config file portwatchz watch --config custom.conf
-
Override config settings with command-line options:
# Config sets interval=5, but this overrides to 10 portwatchz watch --interval 10
// Configuration for taking snapshots
pub const Options = struct {
include_established: bool = false,
include_ephemeral: bool = false,
process_info: bool = true,
hash_binaries: bool = true,
only_ipv4: bool = false,
only_ipv6: bool = false,
filters: []const []const u8 = &[_][]const u8{},
};
// A snapshot of network ports at a point in time
pub const Snapshot = struct {
records: []PortRecord,
timestamp: i64,
options: Options,
pub fn deinit(self: *Snapshot, allocator: std.mem.Allocator) void;
};
// Difference between two snapshots
pub const Diff = struct {
opened: []PortRecord,
closed: []PortRecord,
binary_changed: []struct { old: PortRecord, new: PortRecord },
process_changed: []struct { old: PortRecord, new: PortRecord },
timestamp: i64,
pub fn deinit(self: *Diff, allocator: std.mem.Allocator) void;
};// Take a snapshot of current network ports
pub fn snapshot(alloc: std.mem.Allocator, opts: Options) !Snapshot;
// Compare two snapshots and return differences
pub fn diffSnapshots(old: *const Snapshot, new: *const Snapshot, alloc: std.mem.Allocator) !Diff;
// Save/load baselines
pub fn saveBaseline(path: []const u8, snap: *const Snapshot) !void;
pub fn loadBaseline(path: []const u8, alloc: std.mem.Allocator) !Snapshot;
// Convert diff to ECS events for webhooks
pub fn diffToEcsEvents(d: *const Diff, alloc: std.mem.Allocator) ![]EcsEvent;
pub fn sendWebhookBatch(cfg: *const WebhookConfig, events: []const u8) !void;| Platform | Status | Implementation |
|---|---|---|
| Linux | β Complete | /proc/net parsing |
| macOS | β Complete | netstat + lsof |
| macOS | π§ Planned | Native sysctl + libproc |
| FreeBSD | π§ Planned | BSD sysctl |
| OpenBSD | π§ Planned | BSD sysctl |
| NetBSD | π§ Planned | BSD sysctl |
| Windows | π§ Planned | IP Helper API |
Port changes detected (3 total):
Opened ports (2):
tcp 0.0.0.0:8080 [LISTEN] (PID: 1234) /usr/bin/server
udp 127.0.0.1:5353 [LISTEN] (PID: 5678) /usr/bin/mdns
Closed ports (1):
tcp 0.0.0.0:3000 [LISTEN] (PID: 9999) /usr/bin/node
[
{
"@timestamp": "1640995200",
"event": {
"type": "network",
"action": "port_opened"
},
"network": {
"transport": "tcp",
"direction": "ingress"
},
"process": {
"pid": 1234,
"executable": "/usr/bin/server"
},
"host": {
"hostname": "web-server-01",
"ip": ["0.0.0.0"]
}
}
]- Process verification: BLAKE3 hashing detects binary tampering
- Webhook security: HMAC authentication and mTLS support
- Privilege separation: Runs with minimal required privileges
- Filter validation: Input sanitization for all filter patterns
- Fast enumeration: Efficient parsing of OS network tables
- Memory efficient: Streaming processing for large port lists
- Low overhead: Minimal impact on system performance
- Concurrent safe: Thread-safe for library usage
PortWatchZ uses different strategies optimized for each platform:
- Current:
/proc/netfilesystem parsing - Benefits: Direct kernel data access, no external dependencies
- Performance: Excellent for all use cases
- Current:
netstat+lsofcommand parsing - Benefits: Simple, reliable, works without special privileges
- Planned: Native sysctl + libproc APIs
- Future Benefits: ~50% faster enumeration, reduced memory usage, no subprocess overhead
- Planned: Native sysctl API access
- Benefits: Direct kernel PCB (Protocol Control Block) access
- Performance: Expected to match Linux performance characteristics
- Planned: IP Helper API (GetTcpTable, GetUdpTable)
- Benefits: Native Windows networking APIs
- Performance: Optimized for Windows network stack
- Native macOS Implementation: Direct sysctl + libproc APIs for improved performance
- FreeBSD Support: Native BSD sysctl implementation
- OpenBSD Support: Native BSD sysctl implementation
- NetBSD Support: Native BSD sysctl implementation
- Windows Support: IP Helper API implementation
- Enhanced Process Tracking: Better process-to-port mapping across platforms
- Configuration Enhancements: More granular filtering and monitoring options
- Performance Optimization: Reduced memory footprint and faster enumeration
- Container Support: Docker/Podman network namespace awareness
- Historical Analysis: Trend analysis and port usage patterns
- Advanced Alerting: Multiple webhook endpoints and alert routing
- GUI/Web Interface: Optional web-based monitoring dashboard
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
git clone https://github.com/zombocoder/portwatchz.git
cd portwatchz
zig build test # Run all tests
zig build run -- help # Test CLITo add support for a new platform:
- Create
src/enumerator_<platform>.zig - Implement the
enumerate()function - Add platform detection in
src/lib.zig - Add tests for the new platform
Licensed under the Apache License, Version 2.0. See LICENSE for details.