VPNLens Architecture Decision Records (ADR)

Architecture Decision Records documenting major engineering choices and trade-offs.

This document records the major architecture and engineering decisions made during the development of VPNLens. The purpose of this document is to preserve the context and reasoning behind every critical technical choice. It serves as an architectural diary for current maintainers and a guide for future contributors.


Reproducibility as the Central Design Principle

Context

When designing an infrastructure benchmarking platform, the most critical question is what metric matters most. Initial planning focused on speed, efficiency, and scale. However, network benchmarking is inherently volatile, subject to cloud provider routing, hypervisor contention, and kernel states.

Alternatives Considered

  • Focus on Maximum Scalability: Building a distributed system capable of running thousands of tests concurrently to stress-test the cloud provider's macro network.
  • Focus on Real-time Observability: Building a live-streaming dashboard that shows instant fluctuations in network traffic using WebSockets and time-series databases.
  • Focus on Reproducibility: Prioritizing strict isolation, sequential execution, and automated environment teardowns to ensure identical starting states for every test.

Final Decision

Reproducibility was selected as the absolute, non-negotiable central design principle of VPNLens. Every other feature, optimization, or architectural choice is subordinate to this principle.

Rationale

A benchmark that cannot be perfectly reproduced by an independent engineer under identical conditions is scientifically invalid. If a system produces impressive network speeds but the environment cannot be replicated deterministically, the data is useless for engineering decision-making. By prioritizing reproducibility, VPNLens ensures its output represents genuine architectural characteristics of the VPNs, not just random environmental noise.

Consequences

  • Positive: The data generated by VPNLens is highly trustworthy and mathematically comparable across runs.
  • Negative: The platform is significantly slower and less "flashy" than it could be. Features like parallel testing had to be scrapped.
  • Trade-offs: We traded execution speed and concurrent throughput for absolute data integrity.

Future Considerations

As the platform scales to support multi-cloud deployments, maintaining reproducibility will require increasingly strict Infrastructure as Code (IaC) governance to ensure AWS and OCI nodes provide mathematically equivalent baseline configurations.


Two Cloud Servers

Context

The platform requires hosting for a web dashboard, an API backend, a database, the VPN control planes, and a client execution environment to run iperf3 payload generation.

Alternatives Considered

  • Single Server Architecture: Hosting the React frontend, Node.js backend, SQLite database, VPN servers, and the benchmarking scripts on a single large compute instance.
  • Multi-Node Distributed Architecture: Hosting the web stack on one server, the VPN control planes on another, and utilizing a fleet of 10+ client nodes for distributed load testing.
  • Strict Two-Server Architecture: One node dedicated exclusively to the control plane and web stack (Server 1), and one node dedicated exclusively to payload generation and testing (Server 2).

Final Decision

We implemented a strict Two-Server Architecture. Server 1 acts as the Control Plane, and Server 2 acts as the isolated Benchmark Node.

Rationale

Running the web API, the database, and the load generation scripts on the same server induces the "Observer Effect." Network encapsulation (especially WireGuard) is highly CPU-bound. If the backend API processes an HTTP request, or the SQLite database writes a row while an iperf3 test is active, the resulting CPU interrupt steals cycles from the kernel network stack, artificially throttling the VPN throughput. By separating the nodes, Server 2 sits at near-zero utilization until the exact millisecond the benchmark begins.

Consequences

  • Positive: Zero resource contention during benchmarking. Highly accurate throughput and CPU utilization metrics.
  • Negative: Doubled the infrastructure cost and complexity. Introduced the need for secure server-to-server orchestration.
  • Trade-offs: Operational simplicity was sacrificed to preserve benchmarking integrity.

Future Considerations

The architecture will eventually expand to support a "One Control Plane to Many Benchmark Nodes" topology, allowing sequential tests across multiple geographic regions.


Benchmark Node Isolation

Context

Server 2 exists solely to run benchmarks. The question arose regarding what management software or agent should run on Server 2 to listen for commands from the Control Plane.

Alternatives Considered

  • Custom Node.js Agent: Deploying a small Express API on Server 2 to listen for HTTP webhooks to start benchmarks.
  • Message Broker (RabbitMQ/Redis): Having Server 2 subscribe to a pub/sub queue managed by Server 1.
  • Total Isolation (Zero Agent): Running absolutely no custom daemon on Server 2, relying strictly on native OS utilities.

Final Decision

Total isolation. Server 2 runs zero VPNLens-specific background services or listeners.

Rationale

Every active daemon consumes memory and CPU cycles. An Express API listening for webhooks requires a Node.js runtime, which performs periodic garbage collection. If a garbage collection sweep occurs during a latency test, it introduces jitter. By keeping the node completely isolated and utilizing standard SSH for triggering (discussed below), the execution environment remains mathematically pristine.

Consequences

  • Positive: Eliminates background noise. Guarantees the baseline compute state is identical for every protocol tested.
  • Negative: Makes returning data to the backend more complex, requiring the bash script to act as the HTTP client.
  • Trade-offs: We traded modern distributed systems patterns (like pub/sub) for strict bare-metal performance consistency.

Future Considerations

If the platform transitions to ephemeral infrastructure (spinning VMs up and down on demand), maintaining this zero-agent isolation becomes even more critical to keep boot times fast.


Benchmark Execution over SSH

Context

With Server 2 completely isolated and running no listening agents, the Control Plane (Server 1) needed a secure mechanism to trigger the execution of the bash scripts.

Alternatives Considered

  • Ansible Ad-Hoc Commands: Using Ansible to connect and run the bash scripts.
  • Cron Jobs: Having Server 2 poll a database or file every minute to check for pending jobs.
  • Native SSH Execution: Utilizing a Node.js SSH client library (ssh2) to execute a single atomic command over an encrypted tunnel.

Final Decision

We chose native SSH execution from the Node.js backend to the Benchmark Node.

Rationale

SSH is ubiquitous. It requires zero additional dependencies on the target server. By using ED25519 key-pair authentication, the Node.js backend can securely tunnel into Server 2 and execute ./run.sh. It is synchronous enough to confirm the script started, but the connection can be dropped while the script runs autonomously.

Consequences

  • Positive: Highly secure, zero-dependency orchestration.
  • Negative: SSH connections can drop during heavy iperf3 UDP saturation tests due to the NIC dropping TCP keepalives.
  • Trade-offs: We accepted the risk of dropped connections by implementing strict queue timeouts in the backend logic.

Future Considerations

As the number of benchmark nodes increases, managing SSH keys securely at scale may prompt a migration to a lightweight, compiled Go binary agent, though this would violate the strict isolation principle.


Oracle Cloud Infrastructure (OCI)

Context

The project required a cloud provider to host the virtual machines. Real-world internet routing was necessary, eliminating local hypervisors (like VirtualBox or Proxmox).

Alternatives Considered

  • Amazon Web Services (AWS): The industry standard, highly reliable.
  • DigitalOcean / Hetzner: Developer-friendly, predictable pricing.
  • Oracle Cloud Infrastructure (OCI): Enterprise cloud with a generous "Always Free" tier.

Final Decision

OCI was selected as the foundational infrastructure provider.

Rationale

Initially starting as a university project, budget constraints were a primary concern. OCI's "Always Free" tier provides Ampere (ARM-based) compute instances with up to 4 OCPUs and 24GB of RAM, alongside significant outbound bandwidth allowances. This allowed the platform to scale its throughput testing (hitting multi-gigabit speeds) without incurring catastrophic egress costs.

Consequences

  • Positive: Allowed the project to operate continuously at zero cost while delivering high-performance compute.
  • Negative: OCI's Virtual Cloud Network (VCN) Security Lists are notoriously aggressive, requiring significant manual configuration to allow UDP traffic.
  • Trade-offs: We traded a more intuitive cloud console (like DigitalOcean) for raw, cost-effective compute power.

Future Considerations

VPNLens is fundamentally cloud-agnostic due to containerization. Future iterations will utilize Terraform to deploy across AWS, GCP, and Azure to benchmark inter-cloud routing.


Ubuntu Operating System

Context

A standardized Linux distribution was required for both the Control Plane and the Benchmark Node.

Alternatives Considered

  • Debian: Extremely stable, but older packages.
  • Alpine Linux: Highly lightweight, but uses musl libc instead of glibc, which occasionally causes compilation issues for complex networking tools.
  • Ubuntu LTS (Long Term Support): Widespread, modern kernel, predictable lifecycle.

Final Decision

Ubuntu LTS was selected for all infrastructure nodes.

Rationale

Ubuntu provides excellent, native support for wireguard-linux directly in its modern kernel tree. Because Headscale uses a userspace Go implementation (wireguard-go), we had to ensure WireGuard was utilizing the kernel module to make the comparison valid. Ubuntu’s ubiquitous documentation, native Docker support, and predictable apt package management minimized OS-level troubleshooting.

Consequences

  • Positive: Frictionless installation of dependencies (iproute2, iperf3, wireguard-tools).
  • Negative: Slightly higher baseline memory footprint compared to Alpine or Debian minimal.
  • Trade-offs: Traded absolute minimalism for maximum compatibility and ease of use.

Future Considerations

No plans to change the baseline operating system. Future Ansible playbooks will strictly target Ubuntu/Debian environments.


Docker Containerization

Context

Deploying the Node.js backend, React frontend, SQLite database, Caddy reverse proxy, Headscale control server, and WireGuard UI on Server 1.

Alternatives Considered

  • Bare-metal Installation: Using apt to install Node.js, Nginx, and Headscale directly on the host OS.
  • Systemd Services: Managing the lifecycle of individual binaries via systemd unit files.
  • Docker Containerization: Isolating every service into its own Linux namespace.

Final Decision

The entire Control Plane was strictly containerized using Docker.

Rationale

Installing diverse services on a single bare-metal host inevitably leads to "dependency hell" (e.g., conflicting Node.js versions, port collisions). Docker isolates each application, ensuring that the environment defined in the Dockerfile behaves identically on a developer's laptop and the OCI production server. It allows the entire platform to be destroyed and rebuilt predictably.

Consequences

  • Positive: Guaranteed environment parity. Easy rollbacks. Complete isolation of the Headscale and WireGuard control planes.
  • Negative: Docker’s default bridge network MTU (1500) conflicted with WireGuard encapsulation overhead, causing silent packet drops that were difficult to diagnose.
  • Trade-offs: We accepted the complexity of container networking to achieve application immutability.

Future Considerations

Migration to Kubernetes (K8s) was considered but rejected as massive overkill for the platform's current scope. Docker remains the optimal choice for single-node control planes.


Docker Compose

Context

Orchestrating the relationships, networks, and persistent volumes for the six distinct Docker containers running on Server 1.

Alternatives Considered

  • Manual Docker Run Commands: Executing raw docker run commands in a bash script.
  • Kubernetes (Minikube / k3s): A robust container orchestration platform.
  • Docker Compose: A YAML-based declarative configuration tool for multi-container Docker applications.

Final Decision

Docker Compose was chosen to manage the Control Plane topology.

Rationale

Kubernetes introduces massive cognitive and resource overhead. A k3s cluster consumes significant RAM just to maintain its control loop. Docker Compose provides a perfectly scaled solution: it allows declarative definition of internal bridge networks, volume mounts for the SQLite database, and dependency mapping (e.g., ensuring the backend starts before the reverse proxy), all within a single readable docker-compose.yml file.

Consequences

  • Positive: Simple, declarative infrastructure as code that lives in the Git repository.
  • Negative: Does not support horizontal scaling across multiple servers natively (unlike Docker Swarm or K8s).
  • Trade-offs: Traded cluster-level orchestration capabilities for localized simplicity.

Future Considerations

If the Control Plane requires high availability across multiple geographic regions, the Compose configuration will be translated into Kubernetes manifests.


Caddy over Nginx

Context

The Control Plane exposes four distinct subdomains (vpnlens, backend, wg, hs). A reverse proxy is required to route traffic to the correct Docker containers and terminate SSL/TLS.

Alternatives Considered

  • Nginx + Certbot: The industry standard reverse proxy, coupled with Let's Encrypt cron jobs.
  • Traefik: A modern, container-native reverse proxy.
  • Caddy: A lightweight, memory-safe web server written in Go with automatic HTTPS.

Final Decision

Caddy was selected as the sole edge router and reverse proxy.

Rationale

Managing SSL certificates for multiple subdomains with Nginx is an operational burden. It requires separate sidecar containers, explicit SSL termination blocks, and automated renewal scripts. Caddy natively requests, provisions, and renews Let's Encrypt certificates automatically out of the box. Routing four subdomains required exactly 10 lines of configuration in a Caddyfile.

Consequences

  • Positive: Eliminated an entire class of deployment failures related to expired certificates. Drastically reduced configuration complexity.
  • Negative: Less community documentation for highly esoteric routing edge-cases compared to Nginx.
  • Trade-offs: Traded the ubiquitous familiarity of Nginx for the automated security lifecycle of Caddy.

Future Considerations

No plans to replace Caddy. It perfectly aligns with the project's philosophy of operational simplicity.


React

Context

The platform requires a graphical dashboard to submit benchmark jobs and visualize complex time-series network data.

Alternatives Considered

  • Server-Side Rendering (SSR) via Express/EJS: Rendering static HTML pages on the Node.js backend.
  • Vue.js: A progressive, lightweight JavaScript framework.
  • React: A component-based UI library backed by Meta.

Final Decision

React was selected to build a Single Page Application (SPA).

Rationale

Visualizing CPU jitter and throughput degradation requires sophisticated charting libraries (e.g., Recharts, Chart.js). React’s component-based architecture is exceptionally well-suited for building reusable data visualization widgets. Furthermore, an SPA allows the frontend to aggressively poll or interact with the backend API without triggering full page reloads, providing a smoother experience.

Consequences

  • Positive: Highly modular frontend code. Access to the massive React open-source ecosystem for charting and state management.
  • Negative: Requires a distinct build step and introduces client-side bundle size overhead.
  • Trade-offs: Traded the simplicity of static HTML for the interactive capabilities required by data visualization.

Future Considerations

As the dashboard grows to include historical aggregate comparisons, state management (currently using Context API) may require migration to Redux or Zustand.


Vite

Context

A build tool and development server was required for the React frontend application.

Alternatives Considered

  • Create React App (Webpack): The traditional, officially supported React boilerplate.
  • Next.js: A robust SSR framework.
  • Vite: A modern, fast build tool utilizing native ES modules.

Final Decision

Vite was chosen over Create React App.

Rationale

Vite offers near-instantaneous Hot Module Replacement (HMR) during local development by skipping the bundling process and serving native ES modules directly to the browser. Webpack often took several seconds to recompile after a minor CSS change. Vite aligned perfectly with our desire to maximize engineering velocity and minimize local tooling friction.

Consequences

  • Positive: Extremely fast development loop and highly optimized production builds via Rollup.
  • Negative: Minor configuration differences compared to legacy Webpack setups.
  • Trade-offs: Traded the established legacy of Webpack for modern build speeds.

Future Considerations

Vite remains the standard for the foreseeable future.


Node.js and Express

Context

The Orchestration Layer requires an API to accept jobs, manage a queue, trigger SSH commands, and interface with the database.

Alternatives Considered

  • Python (FastAPI/Django): Excellent for data science, highly readable.
  • Go: Extremely fast, compiled, native concurrency.
  • Node.js (Express): Asynchronous, event-driven JavaScript runtime.

Final Decision

Node.js with the Express framework was selected for the backend.

Rationale

The primary responsibility of the VPNLens backend is waiting. It waits for the database to write. It waits for SSH connections to establish. It waits for bash scripts to execute over the network. Node.js's asynchronous, non-blocking I/O model is perfectly designed for orchestrating external processes without consuming massive amounts of thread memory. Express was chosen for its minimalist, unopinionated routing structure.

Consequences

  • Positive: High concurrency handling. Shared language (JavaScript) between the frontend and backend, reducing cognitive load for contributors.
  • Negative: Single-threaded nature means heavy data parsing (e.g., aggregating thousands of benchmark rows) can temporarily block the event loop.
  • Trade-offs: Traded the raw computational speed of Go for the rapid development and asynchronous ease of Node.js.

Future Considerations

If the backend logic becomes heavily CPU-bound (e.g., complex statistical analysis of historical datasets), those specific routes may be offloaded to Go microservices or Python worker processes.


SQLite instead of PostgreSQL

Context

The platform requires persistent relational storage for job states, unique reporting URLs, and raw numerical metrics.

Alternatives Considered

  • MongoDB: NoSQL database, highly flexible schemas.
  • PostgreSQL: The industry standard for robust, high-concurrency relational data.
  • SQLite: Embedded, file-based relational database.

Final Decision

SQLite was explicitly chosen over PostgreSQL.

Rationale

In enterprise applications, PostgreSQL is mandatory for handling concurrent writes from thousands of users. However, VPNLens enforces strict sequential execution. The queue guarantees that only one benchmark writes to the database at a time (roughly once every 5-10 minutes). High concurrency is a non-issue. SQLite runs completely embedded within the Node.js process, eliminating the need for a separate database container, slashing memory consumption on the Control Plane, and reducing backups to copying a single .sqlite file.

Consequences

  • Positive: Drastically reduced infrastructure complexity. Zero configuration required.
  • Negative: Cannot scale horizontally. If we deploy multiple backend Node.js instances behind a load balancer, they cannot safely write to the same SQLite file.
  • Trade-offs: Traded horizontal write scalability for operational simplicity and minimal memory footprint.

Future Considerations

When the platform evolves to support multiple distributed Benchmark Nodes running tests in parallel, SQLite will encounter write-lock contention. The backend ORM is configured to allow a seamless migration to PostgreSQL when this scaling threshold is reached.


Headscale instead of Tailscale SaaS

Context

The project required evaluating a peer-to-peer, stateful mesh VPN architecture.

Alternatives Considered

  • Tailscale SaaS: The proprietary, managed coordination server provided by Tailscale Inc.
  • Netmaker / Netbird: Alternative open-source mesh VPNs.
  • Headscale: The open-source, self-hosted implementation of the Tailscale control plane.

Final Decision

Headscale was selected to represent the stateful mesh architecture.

Rationale

To conduct a fair, scientifically valid comparison against WireGuard (which is completely self-hosted), the mesh architecture also had to be fully self-hosted. Relying on Tailscale's proprietary SaaS control plane would introduce unpredictable WAN latency into the Connection Establishment and Recovery metrics, as the benchmark node would have to authenticate against Tailscale's public servers rather than our controlled OCI environment. Headscale guaranteed that the entire control loop remained within our measurable infrastructure.

Consequences

  • Positive: Total control over the network environment. Guaranteed fairness in control-plane latency.
  • Negative: Headscale is notoriously difficult to configure automatically via bash compared to Tailscale's polished SaaS onboarding.
  • Trade-offs: We accepted significant deployment complexity to preserve the integrity of the experimental environment.

Future Considerations

Netbird and Nebula are slated for future inclusion to expand the breadth of the mesh architecture comparisons.


WireGuard as the Baseline

Context

The project needed a centralized, point-to-point VPN protocol to serve as the performance baseline against which all other protocols would be measured.

Alternatives Considered

  • OpenVPN: The legacy industry standard.
  • IPsec (StrongSwan): The enterprise standard, but incredibly complex to configure cleanly via automation.
  • WireGuard: Modern, streamlined, kernel-integrated protocol.

Final Decision

WireGuard was selected as the baseline architecture.

Rationale

WireGuard represents the theoretical maximum performance currently achievable by an encrypted overlay network in the Linux kernel. It is lightweight (under 4,000 lines of code), stateless, and integrated directly into the wireguard-linux kernel tree. Measuring any userspace mesh VPN against WireGuard immediately highlights the specific computational tax incurred by utilizing userspace data planes and stateful routing.

Consequences

  • Positive: Provides a clear, high-performance ceiling for benchmarking.
  • Negative: Its stateless nature required us to explicitly define a distinct methodology for measuring "Recovery Time," as it does not perform a traditional handshake when an interface comes back online.
  • Trade-offs: Traded the legacy ubiquity of OpenVPN for modern cryptographic and routing efficiency.

Future Considerations

OpenVPN and IPsec will be added to the automation suite to provide historical context, demonstrating the performance leap WireGuard achieved over legacy protocols.


REST instead of WebSockets

Context

The React frontend needs to know when a benchmark job (which takes up to 10 minutes) is complete, or view its live progress.

Alternatives Considered

  • Server-Sent Events (SSE): Unidirectional event streaming.
  • WebSockets: Persistent, bi-directional, real-time TCP connections streaming stdout from the Benchmark Node.
  • REST Polling / Asynchronous Webhooks: Standard HTTP requests.

Final Decision

Standard REST was maintained, leading to asynchronous email delivery (detailed below), specifically avoiding WebSockets.

Rationale

Streaming the live terminal output of iperf3 to the React dashboard via WebSockets sounds impressive. However, WebSockets require persistent TCP connections. In a cloud environment, reverse proxies (Caddy), load balancers, and mobile ISP networks frequently cull idle or long-running TCP connections. If the WebSocket drops during a 10-minute test, the user loses visibility, and reconciling the UI state with the backend state becomes a complex engineering nightmare.

Consequences

  • Positive: The backend remains stateless and highly resilient to network drops.
  • Negative: The UI cannot display a live, real-time progress bar of the iperf3 execution.
  • Trade-offs: We traded real-time UI flashiness for bulletproof infrastructure stability.

Future Considerations

If live monitoring becomes a strict requirement, WebSockets may be introduced purely as a cosmetic layer, but the core data flow will remain asynchronous REST.


Email Notifications & Resend

Context

Because benchmarks take 5-10 minutes, forcing a user to stare at a loading spinner on a REST endpoint inevitably leads to browser timeouts and abandoned sessions.

Alternatives Considered

  • Local SMTP Server (Postfix): Hosting our own mail server on the Control Plane.
  • Dashboard Polling: Having React silently ping /api/queue/status every 10 seconds.
  • Transactional Email API (Resend): Offloading delivery to a specialized provider.

Final Decision

We implemented asynchronous email notifications utilizing the Resend API.

Rationale

When a job is submitted, the API immediately returns 202 Accepted. The user can close their laptop. Once the bash scripts finish, the backend compiles the data and triggers an email. This is the industry standard for long-running compute jobs. We chose Resend over local SMTP because local mail servers originating from cloud provider IPs are almost universally blacklisted by Gmail and Outlook. Resend guaranteed high deliverability with a modern, developer-friendly SDK.

Consequences

  • Positive: Excellent user experience. Decouples the UI session from the infrastructure execution state.
  • Negative: Introduces a dependency on a third-party SaaS provider.
  • Trade-offs: Traded absolute self-hosting purity for guaranteed message delivery.

Future Considerations

Support for webhooks (e.g., posting results to a Slack or Discord channel) will be added to supplement email notifications.


Unique Report URLs

Context

How should users access their specific benchmark results after receiving the email notification?

Alternatives Considered

  • Sequential IDs: e.g., /results/1, /results/2.
  • PDF Attachments: Generating a PDF and attaching it to the email.
  • Unique Cryptographic URLs: Utilizing UUIDv4 hashes, e.g., /results/f47ac10b-58cc...

Final Decision

Results are accessed exclusively via unique, permanent URLs driven by UUIDv4 hashes.

Rationale

Sequential integer IDs are inherently insecure; a malicious user could write a script to scrape /results/1 through /results/1000, exposing everyone's infrastructure metrics. UUIDv4 tokens contain 122 bits of randomness, making them mathematically impossible to enumerate. Generating a dynamic URL allows the user to view the interactive charts on the React frontend, which is vastly superior to a static PDF attachment.

Consequences

  • Positive: Secure, unguessable, permanent, and easily shareable reports.
  • Negative: URLs are long and cannot be easily memorized.
  • Trade-offs: Traded URL aesthetics for cryptographic security.

Future Considerations

Authentication will eventually be added, allowing users to log in and view a dashboard of all historical UUIDs associated with their account.


Sequential Benchmark Execution

Context

Multiple users might submit benchmark requests simultaneously. How should the execution engine handle concurrency?

Alternatives Considered

  • Parallel Execution: Running multiple bash scripts on Server 2 simultaneously.
  • Load Balancing: Spinning up multiple Server 2 instances and distributing the load.
  • Sequential Queue: A strict First-In-First-Out (FIFO) pipeline.

Final Decision

Benchmarks are executed strictly sequentially via a backend FIFO queue.

Rationale

Running two iperf3 throughput tests simultaneously on the same Server 2 instance is catastrophic to the methodology. The operating system will split the physical Network Interface Card (NIC) bandwidth and CPU time scheduling between the two processes. A tunnel capable of 1 Gbps will report 500 Mbps, rendering both sets of data entirely invalid. The queue acts as a mutex lock on the Benchmark Node.

Consequences

  • Positive: Guarantees 100% of the node's compute and network resources are dedicated to a single test.
  • Negative: Platform throughput is severely capped. A queue of 10 jobs could take an hour to process.
  • Trade-offs: Traded platform scalability for absolute data accuracy.

Future Considerations

Scaling will be achieved by deploying multiple independent Benchmark Nodes, allowing parallel jobs across different servers, while maintaining strict sequential execution per server.


One Active VPN at a Time

Context

When running sequentially, the script must switch between WireGuard and Headscale.

Alternatives Considered

  • Policy Routing: Leaving both tunnels active and using advanced iptables and IP rules to force iperf3 traffic down specific interfaces.
  • Stop and Start: Tearing down Protocol A completely before bringing up Protocol B.

Final Decision

Only one overlay network is permitted to exist in the Linux kernel routing table at any given time.

Rationale

Both WireGuard and Headscale inject default routes and aggressive iptables rules. Leaving both active risks routing collisions, where traffic intended for the Headscale tunnel leaks into the WireGuard interface. Furthermore, an active Headscale daemon consumes background RAM. To ensure a level playing field, the switch.sh script executes a "Stop Everything" phase, flushing all routes and interfaces, guaranteeing Protocol B starts with the exact same pristine kernel state as Protocol A.

Consequences

  • Positive: Eliminates routing leaks and background resource contention.
  • Negative: Adds a few seconds to the total benchmark execution time for teardown and verification.
  • Trade-offs: Traded execution speed for environmental determinism.

Future Considerations

This design pattern is permanent. It is the only reliable way to benchmark invasive kernel modules.


Modular Shell Scripts

Context

The physical execution of the tests requires automating the Linux environment.

Alternatives Considered

  • A Single Monolithic Script: One large benchmark.sh file handling everything.
  • Python Orchestration: Using Python subprocess modules to execute system commands.
  • Modular Bash Scripts: Breaking the logic into run.sh, switch.sh, and run-benchmark.sh.

Final Decision

The automation layer is strictly divided into modular bash scripts.

Rationale

Python was rejected to keep the Benchmark Node free of runtime dependencies. A single monolithic bash script was attempted early in development, but it proved too fragile. If a metric collection command failed, the monolith would crash, leaving the VPN interface permanently active and breaking all subsequent runs.

Consequences

  • Positive: Highly maintainable code. Single Responsibility Principle applied to infrastructure automation.
  • Negative: Passing variables and exit codes between parent and child bash scripts requires rigid error handling.
  • Trade-offs: Traded the simplicity of a single file for robust error isolation.

Splitting run.sh, switch.sh, and run-benchmark.sh

Context

Following the decision to use modular scripts, the specific responsibilities had to be delineated.

Final Decision

  • run.sh: Master orchestrator. Handles SSH invocation, global error trapping, and sequencing.
  • switch.sh: Network mutator. ONLY handles bringing interfaces up/down and verifying routes.
  • run-benchmark.sh: Payload generator. ONLY handles iperf3, ping, sampling, and JSON generation.

Rationale

Decoupling state management (switch.sh) from measurement (run-benchmark.sh) guarantees extensibility. If a contributor wants to add support for OpenVPN, they only need to modify switch.sh to handle OpenVPN's initialization logic. They do not need to touch run-benchmark.sh, because that script blindly generates TCP payloads across whatever interface happens to be active. This separation prevents regressions.


GitHub Actions (CI/CD)

Context

The platform code must be compiled and deployed to the OCI servers.

Alternatives Considered

  • Manual Deployment: git pull on the server, followed by npm run build.
  • Jenkins: Self-hosted automation server.
  • GitHub Actions: Native CI/CD integrated into the repository.

Final Decision

GitHub Actions was selected to automate container builds and publishing.

Rationale

Manual deployment onto production servers leads to downtime and configuration drift (compiling React on a production server consumes massive RAM). Jenkins requires dedicating an entire server just to run CI pipelines. GitHub Actions is free, requires zero server maintenance, and allows us to build multi-architecture Docker images in the cloud, pushing them to a registry where Server 1 can pull them seamlessly.

Consequences

  • Positive: Immutable artifacts. Zero-downtime deployments.
  • Negative: Initial setup for multi-architecture (ARM/AMD) builds via Docker Buildx was complex.
  • Trade-offs: Traded local deployment simplicity for enterprise-grade deployment reliability.

Postponing Infrastructure-as-Code (Terraform/Ansible)

Context

The OCI servers currently require manual clicking in the cloud console to provision, and manual apt-get commands to install initial dependencies.

Alternatives Considered

  • Halt Development: Stop working on the backend until Terraform and Ansible are perfect.
  • Postpone IaC: Accept manual server provisioning for v1.0 to focus on the core benchmarking logic.

Final Decision

Terraform and Ansible were intentionally postponed from the initial release scope.

Rationale

You cannot automate the deployment of a broken system. Early in the project, the bash orchestration scripts were failing due to MTU issues and Headscale control-plane timeouts. Spending weeks writing Terraform to automatically deploy a system that generated invalid data was a misallocation of engineering resources. The priority was stabilizing the core execution loop.

Consequences

  • Positive: Allowed the team to focus 100% on benchmarking accuracy and scripting reliability.
  • Negative: Reproducing the infrastructure currently requires following a manual setup guide.
  • Trade-offs: Traded absolute deployment automation for core functional stability.

Future Considerations

IaC is the immediate next priority for the Medium-Term Roadmap to ensure full platform reproducibility.


Postponing Prometheus and Grafana

Context

Network metrics need to be collected and visualized.

Alternatives Considered

  • Prometheus / Grafana: The industry standard for time-series observability.
  • Custom SQLite + React Dashboard: Building a bespoke ingestion and visualization layer.

Final Decision

Prometheus and Grafana were intentionally not adopted. We built a custom SQLite and React architecture.

Rationale

Prometheus is designed for passive, continuous monitoring of long-lived production systems (e.g., scraping a web server every 15 seconds forever). VPNLens performs aggressive, point-in-time synthetic stress tests. A benchmark generates a distinct, isolated block of data representing a 60-second window. Storing discrete benchmark reports in a relational database (SQLite) makes it infinitely easier to generate static comparative reports (WireGuard vs. Headscale) than querying arbitrary time windows in a time-series database.

Consequences

  • Positive: Lower infrastructure overhead. Tighter integration between the UI and the data model.
  • Negative: Required manually writing charting components in React.
  • Trade-offs: Traded off-the-shelf observability tools for a highly customized, report-driven architecture.

Postponing JWT Authentication

Context

The React dashboard allows anyone to submit a benchmark, which consumes cloud resources.

Alternatives Considered

  • Implement OAuth / JWT immediately: Lock down the dashboard before release.
  • Leave it public: Rely on edge-level rate limiting (Caddy/Express).

Final Decision

Explicit user authentication (JWT) was postponed for v1.0.

Rationale

As an academic and open-source project, removing friction is critical for adoption. Forcing a student or a reviewing engineer to register an account, verify an email, and manage a password just to test the platform would severely limit engagement. Security is currently managed by strict rate limiting and the fact that the underlying SSH keys are never exposed to the frontend.

Consequences

  • Positive: Frictionless user experience.
  • Negative: Susceptible to spam if the endpoint is discovered by malicious actors.
  • Trade-offs: Traded enterprise-grade access control for open-source accessibility.

Future Considerations

As the platform scales to support multi-cloud deployments, JWT and API Keys will become mandatory to prevent unauthorized cloud compute costs.


Conclusion

Engineering is rarely an exercise in finding the universally "correct" answer; it is a continuous series of informed trade-offs.

Every decision documented in this Architecture Decision Record—from isolating the Benchmark Node, to selecting SQLite, to explicitly forbidding parallel execution—involved sacrificing one capability (speed, simplicity, or scalability) to secure another. In the case of VPNLens, operational convenience and execution speed were consistently sacrificed to guarantee absolute metric isolation and reproducibility.

VPNLens intentionally documents these decisions so that future contributors understand why the project looks the way it does. Code tells you what a system does; architecture decision records tell you why it does it. When future engineers attempt to replace SQLite with PostgreSQL or migrate the bash scripts to Go, they will do so understanding the historical context and the physical constraints that shaped the original platform.