-
Notifications
You must be signed in to change notification settings - Fork 64
nftables Monitoring Internals
Technical deep-dive. This page documents the internals of Coi's nftables network monitoring — the LOG-rule layout, kernel log format, detection pipeline, and threat model. For user-facing setup and configuration, see Security Monitoring.
Coi's nftables-based network monitoring provides event-driven, kernel-level visibility into all container network activity. Unlike polling-based monitoring that samples /proc/net/tcp every 2 seconds, nftables monitoring captures every network event at the kernel level, including short-lived connections, blocked attempts, and DNS queries.
Container Network Activity
↓
ip filter FORWARD chain (kernel)
↓ (LOG rules inserted at the top of the chain)
Kernel logs → journald
↓
NFT Daemon (internal/nftmonitor/)
├─ JournalReader: Stream kernel logs
├─ LogReader: Parse nftables entries
├─ NetworkDetector: Analyze threats
└─ Daemon: Orchestrate + integrate with responder
↓
ThreatEvent → Responder (pause/kill) + AuditLog
CRITICAL Level:
-
Metadata endpoint access -
169.254.169.254(cloud provider metadata) - Suspicious ports - 4444, 5555, 1234, 31337, 12345 (common C2/backdoor ports)
HIGH Level:
- RFC1918 private networks - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (should be blocked by firewall)
- Allowlist violations - Connections outside configured allowlist (in allowlist mode)
WARNING Level:
- DNS query anomalies - Queries to unexpected DNS servers
- High DNS volume - >100 queries/minute (potential DNS tunneling)
- Short-lived connections - HTTP requests <2 seconds that polling would miss
- Blocked attempts - Connection attempts rejected by firewall (still logged)
- DNS queries - All port 53 traffic (volume and destination servers)
- All connection attempts - Even connections that fail immediately
# Automated setup (recommended)
./scripts/install-nft-deps.sh
# Manual setup
sudo apt-get install -y libsystemd-dev nftables
sudo usermod -a -G systemd-journal $USER
# Configure passwordless sudo for nft commands
echo '%incus-admin ALL=(ALL) NOPASSWD: /usr/sbin/nft' | sudo tee /etc/sudoers.d/coi-nft
sudo chmod 0440 /etc/sudoers.d/coi-nftIMPORTANT: Log out and log back in (or run newgrp systemd-journal) for group membership to take effect.
# Run health check
coi health
# Test journal access
journalctl -k -n 10
# Test nftables access
sudo -n nft list ruleset| Package | Purpose | Required For |
|---|---|---|
libsystemd-dev |
systemd development headers | Building Coi with NFT support |
nftables |
Kernel packet filtering | Runtime network monitoring |
systemd-journal group |
Read kernel logs without sudo | Runtime log access |
Passwordless sudo (via /etc/sudoers.d/coi-nft) |
Manage nft rules without prompts | Rule creation/deletion |
# ~/.coi/config.toml
[monitoring]
enabled = true # Master switch for all monitoring
auto_pause_on_high = true # Pause container on HIGH threats
auto_kill_on_critical = true # Kill container on CRITICAL threats
[monitoring.nft]
enabled = true # Enable nftables network monitoring (disabled by default)
rate_limit_per_second = 100 # Log volume limit for normal traffic
dns_query_threshold = 100 # Alert if >N DNS queries/minute
log_dns_queries = true # Separate DNS logging (port 53)
lima_host = "" # For macOS: "lima-default" (empty for Linux)NFT monitoring uses tiered rate limiting to prevent log explosion:
-
Unlimited - Always logged:
- Suspicious traffic (metadata, RFC1918, C2 ports)
- DNS queries (port 53)
-
Rate Limited - Limited to N packets/second:
- Normal traffic (default: 100/second)
This ensures critical threats are never missed while preventing performance impact from high-volume benign traffic.
When a container starts, Coi inserts LOG rules at the top of the ip filter FORWARD chain (nftables has no per-rule priority — only chains carry one; nft insert places these ahead of any later verdict rules):
# Rule 1: Always log suspicious destinations (no rate limit)
nft insert rule ip filter FORWARD \
ip saddr 10.47.62.50 \
ip daddr { 169.254.169.254, 192.168.0.0/16 } \
log prefix "NFT_SUSPICIOUS[10.47.62.50]: "
# Rule 2: Always log DNS queries
nft insert rule ip filter FORWARD \
ip saddr 10.47.62.50 udp dport 53 \
log prefix "NFT_DNS[10.47.62.50]: "
# Rule 3: Rate-limited logging for all other traffic
nft insert rule ip filter FORWARD \
ip saddr 10.47.62.50 \
limit rate 100/second \
log prefix "NFT_COI[10.47.62.50]: "Key Points:
- LOG rules sit at the top of the FORWARD chain, so they log before any firewall verdict rules further down
- No verdict - packets continue to firewall rules
- Scoped by container IP - only logs that container's traffic
- Unique prefix allows filtering in journald
Feb 9 12:34:56 kernel: NFT_COI[10.47.62.50]: IN=incusbr0 OUT=eth0 SRC=10.47.62.50 DST=8.8.8.8 PROTO=TCP SPT=54321 DPT=53 SYN
Parsed into:
NetworkEvent{
Timestamp: 2026-02-09 12:34:56,
ContainerIP: "10.47.62.50",
SrcIP: "10.47.62.50",
DstIP: "8.8.8.8",
SrcPort: 54321,
DstPort: 53,
Protocol: "TCP",
Flags: "SYN",
}1. Network packet → nftables LOG rule
2. Kernel log entry → journald
3. JournalReader streams log (via go-systemd)
4. LogReader parses NFT_* entries → NetworkEvent
5. NetworkDetector analyzes event:
- isRFC1918(dst) → HIGH
- dst == 169.254.169.254 → CRITICAL
- isSuspiciousPort(dpt) → CRITICAL
- inAllowlist(dst) → HIGH (if violated)
- analyzeDNSQuery() → WARNING (anomalies)
6. ThreatEvent → Responder → pause/kill + AuditLog
Network events are logged to:
~/.coi/audit/<container-name>-nft.jsonl
Format: JSON Lines (one JSON object per line)
Example Entry:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2026-02-09T12:34:56Z",
"level": "critical",
"category": "network",
"title": "Metadata endpoint access",
"description": "Attempted connection to cloud metadata endpoint",
"evidence": {
"timestamp": "2026-02-09T12:34:56Z",
"container_ip": "10.47.62.50",
"src_ip": "10.47.62.50",
"dst_ip": "169.254.169.254",
"dst_port": 80,
"src_port": 54321,
"protocol": "TCP",
"flags": "SYN"
},
"action": "killed"
}# View all network security events
cat ~/.coi/audit/coi-abc-1-nft.jsonl | jq
# Filter by severity
jq 'select(.level == "critical")' ~/.coi/audit/coi-abc-1-nft.jsonl
# Count events by category
jq -r '.title' ~/.coi/audit/coi-abc-1-nft.jsonl | sort | uniq -c| Feature | Polling (/proc/net/tcp) | nftables + journald |
|---|---|---|
| Short-lived connections | ❌ Misses <2s connections | ✅ Catches all |
| Blocked attempts | ❌ Not visible | ✅ Logged before drop |
| DNS queries | ❌ Not visible | ✅ Port 53 traffic |
| Real-time | ❌ 2-second delay | ✅ Immediate (ms) |
| Performance | ❌ CPU overhead (polling) | ✅ Event-driven |
| Security | ✅ Kernel-level | |
| Completeness | ✅ All attempts | |
| Setup complexity | ✅ No dependencies | |
| Log volume | ✅ Low (snapshots) |
*Rate limiting keeps log volume manageable (default: 100 packets/second for normal traffic)
NFT monitoring works on macOS via Lima VM. The daemon automatically detects Lima and wraps nft commands:
// Automatic detection
if cfg.NFT.LimaHost != "" {
cmd = exec.Command("limactl", "shell", cfg.NFT.LimaHost, "sudo", "nft", ...)
} else {
cmd = exec.Command("sudo", "nft", ...)
}Configuration:
[monitoring.nft]
lima_host = "lima-default" # Or your Lima instance nameCheck health:
coi health --format=json | jq '.checks | {nftables, systemd_journal, libsystemd}'Common Issues:
-
nftables not installed
sudo apt-get install -y nftables
-
No journal access
sudo usermod -a -G systemd-journal $USER # Log out and log back in
-
Sudo password required
# Check sudoers file sudo cat /etc/sudoers.d/coi-nft # Should allow NOPASSWD for nft commands
-
libsystemd-dev missing
sudo apt-get install -y libsystemd-dev # Rebuild Coi: go build ./...
If rules persist after session ends:
# List rules for specific container IP
sudo nft list ruleset | grep "NFT_.*\[10.47.62.50\]"
# Manual cleanup (replace IP)
sudo nft -a list ruleset | grep "NFT_.*\[10.47.62.50\]" | \
grep -oP 'handle \K\d+' | xargs -I {} sudo nft delete rule ip filter FORWARD handle {}If seeing too many log entries:
-
Reduce rate limit:
[monitoring.nft] rate_limit_per_second = 50 # Lower from default 100
-
Disable DNS logging:
[monitoring.nft] log_dns_queries = false # Only suspicious traffic + general
-
Check for chatty applications:
# View top destination IPs sudo journalctl -k | grep "NFT_COI" | grep -oP 'DST=\K[0-9.]+' | sort | uniq -c | sort -rn | head
CPU Overhead: <2% (journald streaming is efficient)
Memory: ~20MB for daemon process
Disk I/O:
- Default: ~100 packets/second logged
- Suspicious traffic: Unlimited (always logged)
- Log rotation handled by systemd
Latency: <100ms from network event to threat alert
Comprehensive Python integration tests available:
# Run all NFT monitoring tests
pytest tests/integration/test_nft_monitoring.py -v
# Run specific test class
pytest tests/integration/test_nft_monitoring.py::TestNetworkThreatDetection -v
# Run with detailed output
pytest tests/integration/test_nft_monitoring.py -vvsTest Coverage:
- Rule creation and deletion
- Threat detection (metadata, RFC1918, suspicious ports, DNS)
- Audit logging (format, content)
- Daemon lifecycle
- Health checks
- Edge cases (high volume, multiple containers)
- Tamper-proof - Cannot be disabled from inside container
- Complete visibility - Catches all connection attempts (even blocked ones)
- Fast connections - No polling gap - <100ms connections are captured
- Defense in depth - Works even if container process monitoring is compromised
Protects Against:
- ✅ Exfiltration via short HTTP requests
- ✅ DNS tunneling (query volume detection)
- ✅ C2 connections on non-standard ports
- ✅ Metadata endpoint access (cloud credentials)
- ✅ Lateral movement to private networks
Does Not Protect Against:
- ❌ Encrypted payload inspection (no DPI)
- ❌ Domain name visibility (only IP addresses and ports)
- ❌ Application-layer attacks (use WAF/IDS)
NFT monitoring only logs:
- Source/destination IP addresses and ports
- Protocol (TCP/UDP/ICMP)
- Flags (SYN, ACK, etc.)
Not logged:
- Packet payloads
- Domain names (only resolved IPs)
- HTTP headers or paths
- TLS/SSL encrypted content
Potential future improvements:
- DNS payload inspection - Integrate with systemd-resolved for domain visibility
- Connection tracking - Track full connection lifecycle (SYN → FIN)
- Traffic statistics - Bandwidth usage per destination
- Custom rules - User-defined nftables rules for specific threats
- Integration with fail2ban - Automatic IP blocking for repeated violations
Home · Getting Started · Configuration · Migration Guide · GitHub · Issues
Getting Started
Setup
Configuration & Usage
- Best Practices
- Configuration
- Profiles
- Supported Tools
- Container Lifecycle & Sessions
- Container Operations
- Snapshot Management
- File Transfer
- Port Publishing
- Tmux Automation
- Headless Orchestration
- Image Management
- Resource & Time Limits
- Resource Usage (coi top)
Security
- Threat Model: Containment Limits
- Security Monitoring
- Audit Log
- Session Logs
- Security Best Practices
- Network Isolation
Maintenance
Help & Reference