Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The PAC HTTP Server and Service Lifecycle

Crate: kinetic-pac Stage: N/A (Supplemental to core Kinetic) Reading time: 20 minutes Depends on: 01_overview.md, 02_pac_main_1.md


What Is This?

This file covers the second half of the kinetic-pac daemon implementation. It specifically covers lines 231 through 461 in kinetic-pac/src/main.rs. The primary focus of this file is the dynamic server loop. This loop stays alive in the background to serve the Proxy Auto-Configuration (PAC) file. It serves this file directly to the host operating system.

It also thoroughly covers the daemon’s service management lifecycle commands. These commands dictate how kinetic-pac registers itself as a native background service. They explain how it gracefully handles uninstalls. Crucially, they explain how it manages termination events. This ensures the user’s internet connection does not break when the daemon stops.

Rather than relying on a static .pac file stored somewhere on the user’s hard drive, kinetic-pac launches an active HTTP web server. This server is bound to 127.0.0.1:16001. This server generates the PAC file on-the-fly. It does this every single time the operating system’s network stack requests it.

This is a fundamental architectural decision for the Kinetic ecosystem. It allows the system to respond instantly to changes in the peer-to-peer network. This includes new nodes coming online. This includes nodes disconnecting. This includes new Top Level Domains (TLDs) being registered. It does all this without ever needing to forcibly reload a static file on the OS side. Every time a browser resolves a new domain, it fetches the most recent set of proxy rules directly from this HTTP server.


Why Kinetic Needs This

If the Kinetic network only operated by writing a static proxy.pac file to your hard drive, it would be severely handicapped. This is because of how operating systems handle proxy configurations. Both Windows and macOS aggressively cache PAC files. If you point the OS to a static file file:///C:/proxy.pac, the OS will read it once. It will load those rules into memory. It will then refuse to read the file again unless the networking adapter is restarted. Alternatively, the user would have to manually toggle their proxy settings off and back on.

When a new peer joins the Kinetic network, it might start hosting a new decentralized domain. The static PAC file would remain entirely unaware of this. The operating system would be completely blind to those real-time network changes. The user would not be able to resolve the new .kinetic domains until they rebooted their network connection.

Kinetic needs a reliable mechanism to push real-time routing changes. These changes must be pushed to the operating system’s proxy resolver seamlessly. They must be pushed transparently.

By running a very lightweight background HTTP server, kinetic-pac sidesteps the OS caching problem entirely. By instructing the OS to fetch the PAC rules from an HTTP URL (http://127.0.0.1:16001/proxy.pac), the OS recognizes that the configuration is dynamic. Whenever the OS needs to make a network request, it pings the local HTTP server. It asks for the latest routing instructions.

The run_server loop intercepts that request. It reads the current state of the Kinetic network from the local filesystem. It instantly compiles a Javascript PAC script. This script is tailored exactly to that split-second snapshot of the network.

Warning

Furthermore, this daemon directly manipulates operating system-level internet settings. Because of this, stability is a paramount concern. If the kinetic-pac daemon were to crash, it would cause major issues. If it were forcefully closed, it would leave the OS proxy settings pointing to a dead local port. The user’s entire internet connection would be broken. Their browser would try to route traffic to 127.0.0.1:16001. It would find nothing there. It would drop the connection.

This specific section of the codebase provides the crucial lifecycle hooks. This includes Unix signal handlers. This includes background service management routines. These hooks guarantee that when the Kinetic node shuts down, the OS proxy settings are restored to their default state. This saves the user from a catastrophic network failure.


How It Works

The lifecycle of the kinetic-pac server can be broken down into five major phases. These execute sequentially when the daemon starts in the background and runs its continuous loop.

1. Directory Initialization and Setup

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 285 to 293
}

Before the server can begin serving any PAC files, it needs a source of truth. It needs to establish a central source of truth for the network’s state. It identifies the user’s local application data directory. It does this using the dirs::data_local_dir() function. Depending on the operating system, this could be AppData/Local on Windows. It could be ~/Library/Application Support on macOS.

It then appends kinetic_global. It creates this base directory if it doesn’t already exist. Inside that folder, it creates a proxies subdirectory.

This folder serves as the central communication bridge. It bridges the core Kinetic network daemons and the PAC server. When other parts of Kinetic successfully establish a tunnel, they act. When they discover a new decentralized peer, they act. They write a simple .json file into this proxies folder. The PAC server, in turn, will read these files. It uses them to understand the current topology of the network.

2. OS Settings Injection

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 294 to 297
}

Once the directories are prepared, the server initializes the PacManager struct. This manager handles OS-specific registry editing and system configuration.

The daemon then defines the pac_url. It defines it as http://127.0.0.1:16001/proxy.pac. It immediately calls pac_manager.install(pac_url). This is the critical moment where control of the network is handed over. This function call injects the local HTTP URL directly into the Windows Registry. Or, it injects it into the macOS SystemConfiguration framework. From this exact line onward, the host operating system is dependent on this background daemon. It depends on it for all internet routing decisions.

3. The Axum HTTP Router and Security Filtering

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 299 to 308
}

To handle the incoming HTTP requests from the OS proxy resolver, Kinetic uses axum. axum is a highly performant asynchronous web framework built by the Tokio team.

It defines a single HTTP GET route. This route listens at /proxy.pac.

Important

Because this server binds to a port on the user’s machine, security is a major concern. We absolutely do not want other devices on the same local Wi-Fi network attempting to query the PAC server. We do not want them discovering the internal proxy configurations.

To prevent this, the route handler acts immediately. The very first thing it does is extract the HTTP Host header. It extracts this from the incoming request. It parses the header. It splits off any port numbers. It checks the raw hostname. If the request does not originate from localhost, it fails. If it does not originate from 127.0.0.1, it fails. If it does not originate from the IPv6 loopback [::1], it fails. The server immediately terminates the request. It returns a 403 Forbidden error. This guarantees that only the host operating system running the daemon can read the PAC rules.

4. Dynamic PAC Script Generation

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 309 to 396
}

If the incoming request passes the security check, the server begins work. It begins constructing a Javascript string in memory. The standard PAC script specification dictates a specific format. The file must contain a specific Javascript function named FindProxyForURL(url, host). The operating system executes this Javascript function for every single network request.

The server begins the script generation by scanning. It scans the kinetic_global/proxies directory. It looks for any files ending in .json.

For each JSON file it finds, it opens it. It reads the contents as a string. It deserializes it into a RegisteredProxy struct using serde_json. It validates the data. It ensures that the proxy_ip field is a valid IP address. If it encounters corrupted or malformed data, it logs a warning. It simply skips the file. This prevents a single bad file from breaking the entire proxy script.

The code then categorizes the proxy. It identifies whether the proxy is a “Native” Kinetic proxy (a direct peer). Or, it identifies if it is an “Atlas” proxy (a specialized relay). It does this by checking the JSON filename. It checks if it starts with the prefix atlas_.

The proxies are grouped together in a HashMap. The key is the Top Level Domain (TLD), such as .kinetic. This hash map uses a tuple. The tuple holds (Option<NativeProxy>, Option<AtlasProxy>). This ensures that for any given domain, the system knows exactly which primary and backup relays are available.

Once the hash map is populated, the code iterates over every discovered TLD. It begins writing the actual Javascript proxy instructions. For each TLD, it constructs a complex proxy fallback string. The fallback chain is constructed as follows:

  • First attempt: PROXY <native_ipv4>:<port>
  • Second attempt: PROXY <native_ipv6>:<port>
  • Third attempt: PROXY <atlas_ipv4>:<port>
  • Fourth attempt: PROXY <atlas_ipv6>:<port>
  • Final fallback: DIRECT

This specific string format is crucial. It tells the operating system’s browser what to do. It says: “Try the Native proxy first.” “If it fails, try the Atlas proxy.” “If both of them fail, bypass the proxy entirely.” “Try connecting directly.”

This string is then injected into a Javascript conditional statement. It uses the PAC shExpMatch function. It writes two identical rules for each domain. This handles variations in trailing dots:

  • Rule 1: if (shExpMatch(host, "*.tld")) return "PROXY_STRING";
  • Rule 2: if (shExpMatch(host, "*.tld.")) return "PROXY_STRING";

Finally, after all TLD rules are written into the script, it adds a final catch-all statement. It places this at the very end of the function: return "DIRECT"; This ensures that all standard internet traffic completely bypasses the Kinetic network. Traffic like google.com or github.com routes normally.

The server sets the HTTP Content-Type header. It sets it to application/x-ns-proxy-autoconfig. It returns the generated Javascript to the OS.

5. Graceful Shutdown via Signal Handlers

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 414 to 432
}

Because kinetic-pac actively altered the host operating system’s network settings in Step 2, it cannot simply exit when closed. If it just abruptly terminated, the OS would keep trying to reach the PAC file at port 16001. It would fail. It would subsequently drop all internet traffic.

To prevent this, the server spawns a dedicated background Tokio task. Its sole purpose is to listen for operating system interrupt signals.

On Unix systems (like macOS or Linux), it registers a listener for SIGTERM. Across all platforms, it registers a listener for standard Ctrl+C interrupts. It uses the tokio::select! macro to wait for any of these signals concurrently.

When a termination signal is received, the background task acts. It blocks the actual shutdown of the program. It immediately calls pac_manager.uninstall(). This goes into the Windows Registry or macOS System Preferences. It completely removes the local HTTP PAC URL. It restores the network settings to their original state. Only after this cleanup is complete does the process call std::process::exit(0). This allows the daemon to safely die. This mechanism guarantees that the user’s internet is not accidentally bricked when they close the application.


Key Pieces

run_server()

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 281 to 436
}

This is the core execution loop of the entire PAC daemon. When invoked, it initializes the tracing subsystem for logging. It creates the required filesystem directories. It mutates the OS network configurations. It binds the high-performance HTTP listener to 127.0.0.1:16001. It runs continuously as a background process until explicitly terminated.

install_service() and uninstall_service()

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 231 to 261
}

These crucial management functions leverage the service_manager crate. They register kinetic-pac as a native, persistent background daemon on the host OS. On Linux, this creates a systemd unit. On macOS, it creates a launchd plist file. On Windows, it creates a native Service. The uninstall_service function is particularly robust. It ensures that it manually wipes the OS PAC settings via PacManager::uninstall(). It does this just in case the service crashed or was forcefully killed previously.

The Route Handler Closure

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 301 to 407
}

This enormous async closure is passed to the axum::routing::get() method. It dictates exactly what happens every single time the OS proxy resolver pings port 16001. It is responsible for the critical Host header security check. It performs the synchronous filesystem I/O to read the proxies directory. It parses the JSON data. It executes the fallback logic. It formats the final raw Javascript string.

The Signal Hook (tokio::select!)

#![allow(unused)]
fn main() {
-> See: crates/kinetic-pac/src/main.rs — Lines 419 to 424
}

This is an advanced asynchronous flow control pattern in Rust. It waits for multiple async events concurrently. It only executes the code block for whichever event finishes first. It listens for either a Unix termination signal or a standard Ctrl+C interrupt. Whichever fires first immediately triggers the cleanup block. This prevents the program from terminating ungracefully.


How This Connects to the Rest of Kinetic

This specific section of the codebase serves as the absolute final consumer of the proxy data within the entire Kinetic ecosystem.

  • Reads from: It continuously monitors the kinetic_global/proxies directory on the local filesystem.
  • Written by: Other completely independent Kinetic daemons (such as kinetic-core or the network layer) are responsible. They make connections and write the .json files into this directory.
  • Controls: It has direct, unchallenged control over the host operating system’s network stack. The Javascript string generated in this file dictates routing. It dictates, on a request-by-request basis, whether a web browser’s traffic routes securely. It decides if it goes into the decentralized Kinetic network. Or, it decides if it bypasses it to the regular internet.

This architecture is deliberately decoupled. The kinetic-pac daemon does not need to know how the network layer works. It does not communicate with the rest of the node via RPC channels. It does not use websockets. It does not use shared memory. It simply blindly serves whatever JSON files happen to exist in the shared folder at that specific millisecond. This makes the proxy layer incredibly resilient to crashes in the networking stack.


Quick Reference

  • Server Bind Address: 127.0.0.1:16001
  • Exposed Route: GET /proxy.pac
  • Primary Data Source: ~/.local/share/kinetic_global/proxies/*.json
  • Security Mechanism: Rejects non-loopback Host headers with a strict 403 Forbidden response.
  • Javascript Output: Dynamically constructs and returns a FindProxyForURL(url, host) function. This maps specific decentralized TLDs to PROXY command strings.
  • Proxy Fallback Chain: Prioritizes Native nodes. It falls back to Atlas relays. It defaults to DIRECT connection.
  • Cleanup Routine: Automatically uninstalls the PAC configuration from the OS registry. It does this upon receiving SIGTERM or Ctrl+C.

Open Questions / Things to Revisit

Note

  • Filesystem I/O on Every Single Request: Currently, the axum route handler executes std::fs::read_dir every single time. It executes this when the operating system requests the PAC file. On high-traffic networks where the OS polls the PAC file frequently, this is a problem. Some operating systems do this every few seconds. This could cause disk thrashing or elevated CPU usage. It would be much more performant to introduce a debounced in-memory cache. Or, use a crate like notify to actively watch the directory for filesystem changes. This is better than synchronously polling the disk inside the async HTTP handler.

Warning

  • Blocking File System Calls in an Async Context: The handler relies on std::fs::read_to_string to parse the JSON files inside an axum async route. In the Tokio runtime ecosystem, utilizing blocking standard library I/O operations inside an asynchronous handler is bad practice. It can unintentionally stall the underlying async executor thread. This logic should be migrated to tokio::fs::read_to_string. This prevents blocking the worker pool.

Important

  • Service Name Hardcoding Fragility: The background service label is generated dynamically. It is generated as format!("{}-pac", kinetic_core::constants::NETWORK_ID). If the NETWORK_ID constant ever changes in the future, older installed services might become orphaned. They would be left running perpetually on the user’s OS. This is because the uninstaller would be looking for a different service label name.