Skip to content

hinryd/router67

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

router67

A tiny local daemon that routes LLM API calls to whichever provider can serve them.

Your apps keep calling api.anthropic.com and friends. router67 quietly takes those hostnames over, reads the model field, and forwards each request to the right upstream — silently falling back through a chain you configure whenever a provider errors or rate-limits.

Quick start

# build + install: generates a local CA, trusts it, aliases 127.0.67.1,
# and seeds ~/.router67/config.toml
cargo build --release
sudo ./target/release/router67 install

# add your API keys (inline or via env vars) and pick your model aliases
$EDITOR ~/.router67/config.toml

# run (needs root to bind :443)
sudo -E ./target/release/router67 daemon

Check state at any time:

./target/release/router67 status

Tear it all down (hosts block, lo0 alias, trusted CA):

sudo ./target/release/router67 uninstall

Config

See config.example.toml. Schema:

intercept_hosts = ["api.anthropic.com", "api.openai.com", "openrouter.ai"]

[daemon]
listen          = "127.0.67.1:443"   # loopback IP + :443
log_level       = "info"
max_connections = 32

[[providers]]
name     = "anthropic"
base_url = "https://api.anthropic.com"
auth     = { header = "x-api-key", value_env = "ANTHROPIC_API_KEY" }
extra_headers = { "anthropic-version" = "2023-06-01" }

[providers.available_models]
"claude-sonnet-4.6" = "claude-sonnet-4-5-20250929"

[[providers]]
name     = "openrouter"
base_url = "https://openrouter.ai/api"
auth     = { header = "Authorization", prefix = "Bearer ", value_env = "OPENROUTER_API_KEY" }

[providers.available_models]
"claude-sonnet-4.6" = "anthropic/claude-sonnet-4.6"

Auth secret

Each provider's auth must specify exactly one of:

  • value_env = "OPENAI_API_KEY" — read the secret from that environment variable at request time (recommended)
  • api_key = "sk-..." — hard-coded inline in the config file (convenient for single-user setups; only use if ~/.router67/config.toml is 0600-restricted)

Routing

Provider order defines the fallback chain. For an incoming request with model = "X", router67 filters the provider list down to those whose available_models contains "X" and tries them in declaration order. available_models["X"] is the name written into the outbound request body.

Failure cascade (next provider is tried) on:

  • transport error (DNS fail, TCP reset, TLS handshake fail, …)
  • HTTP 429
  • HTTP 5xx

Any other status is returned to the client untouched. There are no retries against the same provider — application-layer retries own that.

If every provider in the chain fails, router67 returns 502 with a JSON body listing each attempt.

How the interception works

  • router67 install generates a local root CA under ~/.router67/ca/, adds it to the System keychain via security add-trusted-cert, and aliases 127.0.67.1 onto lo0. All persistent, one-time setup.
  • router67 daemon writes the /etc/hosts block mapping each intercept_hosts entry to the loopback IP, then binds :443 on that IP and terminates TLS using on-the-fly leaf certs signed by the CA (resolved from the client's SNI). When the daemon stops (Ctrl-C / SIGTERM), the hosts block is removed — so your network is only rewritten while the daemon is running.
  • The daemon's own upstream calls use a hand-rolled UDP DNS stub pointed at 1.1.1.1 / 8.8.8.8, bypassing /etc/hosts so dialing api.anthropic.com resolves to the real origin instead of looping back.

Layout

src/
  main.rs      CLI entry
  cli.rs       clap subcommands
  config.rs    TOML schema + load
  providers.rs Provider struct + auth helper
  ca.rs        CA gen + SNI-keyed leaf cert resolver
  hosts.rs     /etc/hosts managed-block editor
  install.rs   install / uninstall (CA trust + hosts)
  dns.rs       minimal UDP DNS stub (bypasses /etc/hosts)
  pool.rs      bounded thread pool
  http_io.rs   sync HTTP/1.1 request parse + response write helpers
  daemon.rs    TcpListener + rustls accept loop + dispatch
  router.rs    model extraction + rewrite
  proxy.rs     upstream attempt via ureq + fallback cascade

Status

MVP. macOS only (hosts file + security CLI integration). Sync (no tokio) with a bounded thread pool, HTTP/1.1 only.

Roadmap

  • Linux / Windows support
  • TUI config + OAuth login
  • OAuth flows
  • Tray icon
  • Config hot-reload
  • launchd / systemd units

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages