Skip to content

Architecture

Overview

foxd is a self-contained Rust daemon designed to passively monitor local area networks. It combines packet capture, kernel netlink monitoring, and event-driven notification delivery into a single binary with an embedded web console.

System Architecture

┌─────────────────────────────────────────────────────────────┐
│                         foxd Daemon                          │
│                                                              │
│  ┌────────────────────┐      ┌───────────────────────────┐ │
│  │   Packet Capture   │      │   Netlink Monitor         │ │
│  │   (libpcap)        │      │   (Linux Neighbor Table)  │ │
│  │                    │      │                           │ │
│  │  • ARP packets     │      │  • Neighbor added events  │ │
│  │  • DHCP packets    │      │  • Neighbor removed       │ │
│  │  • BPF filtering   │      │  • Neighbor updates       │ │
│  └─────────┬──────────┘      └──────────┬────────────────┘ │
│            │                            │                   │
│            └────────┬───────────────────┘                   │
│                     │                                       │
│            ┌────────▼────────────┐                          │
│            │   Event Processor   │                          │
│            │                     │                          │
│            │  • Device discovery │                          │
│            │  • State management │                          │
│            │  • Event correlation│                          │
│            └────────┬────────────┘                          │
│                     │                                       │
│         ┌───────────┴───────────┐                           │
│         │                       │                           │
│   ┌─────▼──────┐        ┌──────▼──────┐                    │
│   │  Database  │        │  Rule Engine │                    │
│   │  (SQLite)  │        │              │                    │
│   │            │        │  • Match     │                    │
│   │  • Devices │        │  • Evaluate  │                    │
│   │  • Rules   │        │  • Trigger   │                    │
│   │  • Logs    │        └──────┬───────┘                    │
│   │  • Channels│               │                            │
│   └─────┬──────┘               │                            │
│         │               ┌──────▼────────┐                   │
│         │               │   Notifier    │                   │
│         │               │               │                   │
│         │               │  • Telegram   │                   │
│         │               │  • ntfy       │                   │
│         │               │  • Webhook    │                   │
│         │               └───────────────┘                   │
│         │                                                   │
│   ┌─────▼──────────────────────────────────┐               │
│   │          REST API & Web Console         │               │
│   │                                         │               │
│   │  • /api/* - JSON endpoints              │               │
│   │  • /* - Embedded SvelteKit console      │               │
│   └─────────────────────────────────────────┘               │
└─────────────────────────────────────────────────────────────┘

Components

1. Packet Capture

Purpose: Passively observe network traffic to detect device activity.

Technology: libpcap (via Rust bindings)

Operation:

  • Captures packets on specified network interface
  • Applies BPF filter (typically ARP and DHCP)
  • Extracts MAC addresses, IP addresses, and hostnames
  • Forwards events to the event processor

Purpose: Track Linux kernel's neighbor table for device state changes.

Technology: rtnetlink (Rust netlink library)

Operation:

  • Subscribes to kernel neighbor table events
  • Receives notifications when devices are added, removed, or updated
  • Provides definitive state information for device online/offline status
  • Runs in parallel with packet capture

Why Both?

  • Packet capture detects activity and discovers new devices quickly
  • Netlink provides authoritative state from the kernel's perspective
  • Combined approach reduces false positives and missed events

3. Event Processor

Purpose: Correlate events and manage device state.

Operation:

  • Receives events from both capture and netlink monitors
  • Maintains in-memory device state
  • Detects first-time device discovery (new devices)
  • Tracks online/offline transitions
  • Updates database with current state
  • Emits high-level events (connected, disconnected, new device)

State Machine per Device:

     ┌─────────┐
     │ Unknown │
     └────┬────┘
          │ First packet/neighbor entry

     ┌─────────┐
     │  Online │◄──┐
     └────┬────┘   │
          │        │ Activity seen
          │ Timeout│
          ▼        │
    ┌──────────┐  │
    │ Offline  │──┘
    └──────────┘

4. Database (SQLite)

Purpose: Persistent storage for all daemon data.

Schema:

  • devices - Discovered devices with MAC, IP, hostname, status
  • rules - Notification rules and triggers
  • notification_channels - Channel configurations
  • logs - Structured log entries

Why SQLite?

  • Zero configuration
  • Single file storage
  • Embedded in-process
  • ACID guarantees
  • No separate server needed

5. Rule Engine

Purpose: Evaluate events against user-defined rules and trigger notifications.

Operation:

  • Receives high-level events (new_device, device_connected, etc.)
  • Loads active rules from database
  • Evaluates each rule against the event
  • Filters by MAC address if specified
  • Triggers matching notification channels

Trigger Types:

  • new_device - First time ever seeing a device
  • device_connected - Device came online (from offline)
  • device_disconnected - Device went offline (from online)
  • device_status_change - Any status transition

6. Notifier

Purpose: Send notifications to external services.

Supported Channels:

Telegram:

  • Uses Telegram Bot API
  • Sends HTML-formatted messages
  • Requires bot token and chat ID

ntfy:

  • Posts to ntfy.sh or self-hosted servers
  • Supports authentication tokens
  • Uses tags and priority levels

Webhook:

  • HTTP POST with JSON payload
  • Custom headers supported
  • Flexible integration with any HTTP endpoint

7. REST API

Purpose: Provide programmatic access to daemon functionality.

Framework: Axum (async Rust web framework)

Endpoints:

  • Device management (/api/devices/*)
  • Rule management (/api/rules/*)
  • Notification channel management (/api/notifications/*)
  • Configuration (/api/config)
  • Metrics and logs (/api/metrics, /api/logs)

8. Web Console

Purpose: User interface for managing foxd.

Technology: SvelteKit + TypeScript

Features:

  • Dashboard with metrics and device list
  • Device management (nicknames, status)
  • Rule creation and editing
  • Notification channel configuration
  • Log viewer

Embedded: Compiled to static assets and embedded into the Rust binary using rust-embed. No separate web server needed.

Data Flow

Device Discovery Flow

1. Packet captured (ARP/DHCP) or neighbor event received

2. Event processor extracts MAC, IP, hostname

3. Check if device exists in database

4. If new: Insert device, mark as "new_device" event
   If existing: Update last_seen, check status change

5. Update device status (online/offline) based on activity

6. Emit event to rule engine

7. Rule engine evaluates all active rules

8. Matching rules trigger notifications via notifier

9. Notifier sends to configured channels

10. Log event and notification in database

API Request Flow

1. HTTP request received by Axum

2. Route matched and handler called

3. Handler extracts parameters and body

4. Database query or configuration read

5. Optional: Update notifier state if channels changed

6. Format response as JSON

7. Return HTTP response to client

Concurrency Model

foxd uses Tokio for async runtime:

  • Packet Capture Thread: Blocking libpcap operations run in dedicated thread
  • Netlink Monitor: Async task subscribing to kernel events
  • Event Processor: Async task processing queued events
  • Database: Wrapped with Arc<RwLock> for safe concurrent access
  • API Server: Tokio async HTTP server with per-request tasks
  • Notifier: Async HTTP clients for sending notifications

Configuration

Configuration is loaded at startup from config.toml:

toml
[daemon]
interface = "eth0"
device_timeout_secs = 300

[database]
path = "./foxd.db"

[api]
host = "0.0.0.0"
port = 8080

Notification channels are stored in the database, not in config file. This allows dynamic management without restarts.

Performance Characteristics

Memory Usage:

  • Base: ~10-20 MB (daemon + web console embedded)
  • Per device: ~1-2 KB in-memory state
  • Database size grows with devices and logs

CPU Usage:

  • Idle: <1% (waiting for events)
  • Active: 2-5% during packet capture and processing
  • Spikes during rule evaluation and notification sending

Network:

  • Passive listening (no active probing)
  • Outbound only for notifications
  • Minimal bandwidth impact

Disk I/O:

  • SQLite writes on every device update
  • Log entries written continuously
  • Configurable log cleanup reduces growth

Security Considerations

Required Privileges:

  • CAP_NET_RAW and CAP_NET_ADMIN for packet capture and netlink
  • Or run as root (not recommended)

Network Exposure:

  • API has no authentication
  • Bind to 127.0.0.1 for localhost-only access
  • Use firewall rules or reverse proxy for external access

Stored Credentials:

  • Telegram bot tokens and ntfy auth tokens stored in SQLite
  • Database file should be protected with file permissions
  • No encryption at rest (consider full-disk encryption)

Dependencies:

  • All Rust crates vetted through cargo audit
  • Minimal external dependencies
  • No npm packages in runtime (only build time for console)

Deployment Patterns

Single Host Monitoring

Most common deployment. foxd runs on the network it monitors:

┌─────────────────────────┐
│  Raspberry Pi / Server  │
│                         │
│  ┌─────────────────┐    │
│  │      foxd       │    │
│  └─────────────────┘    │
│                         │
│  Connected to LAN       │
└──────────┬──────────────┘

    ┌──────▼──────────────────┐
    │      Local Network      │
    │  (Devices to monitor)   │
    └─────────────────────────┘

Multiple Network Segments

Run multiple foxd instances for different network segments:

┌────────────┐         ┌────────────┐
│  foxd #1   │         │  foxd #2   │
│ (Network A)│         │ (Network B)│
└─────┬──────┘         └─────┬──────┘
      │                      │
      │   ┌──────────────┐   │
      └───►   Webhook    ◄───┘
          │  Aggregator  │
          └──────────────┘

Each instance monitors its segment, sends notifications to central webhook.

Container Deployment

foxd can run in Docker with host network mode:

bash
docker run --network=host \
  -v /path/to/config.toml:/etc/foxd/config.toml \
  -v /path/to/data:/var/lib/foxd \
  --cap-add=NET_RAW \
  --cap-add=NET_ADMIN \
  foxd:latest

Future Architecture Considerations

Potential Enhancements:

  • Event streaming (MQTT, Redis pub/sub)
  • Multi-host coordination (shared database)
  • Authentication and RBAC for API
  • Encrypted database with key management
  • Plugin system for custom notifiers
  • Prometheus metrics export
  • Distributed tracing

Design Constraints:

  • Maintain single-binary deployment
  • Keep core functionality simple and focused
  • Avoid cloud dependencies
  • Minimize external service requirements