How it works

When you run silo npm run dev, three things happen:

1. Compute a deterministic IP from the git directory
2. Configure the loopback interface (IP alias + /etc/hosts)
3. Intercept bind() and connect() syscalls in the child process

Your app still thinks it's on localhost:3000. Silo rewrites the address transparently. No code changes needed.

IP computation

Silo hashes the canonical git directory path and the branch name with FNV-1a to produce a deterministic IP in the 127.0.0.0/8 range.

input:  /home/user/project + "feature-auth"
          ↓ FNV-1a → mod 127.0.0.0/8
ip:     127.185.176.25

The first octet is always 127. Octets 2 and 4 are clamped to 1–254 to avoid broadcast and zero addresses. This gives ~16.5 million unique addresses, more than enough for any machine.

Since each git worktree has a different directory path, each gets a different IP. Same directory + same branch = same IP, every time.

Session setup

Before executing your command, silo:

# Add a loopback alias (macOS)
sudo ifconfig lo0 alias 127.185.176.25 netmask 255.0.0.0

# Add a loopback alias (Linux)
sudo ip addr add 127.185.176.25/8 dev lo

# Add /etc/hosts entry
127.185.176.25   feature-auth.project.silo   # /home/user/project

Passwordless sudo is configured once via /etc/sudoers.d/silo (silo prompts on first run). Aliases and hosts entries persist across runs. Use silo prune to clean up.

Syscall interception

Silo intercepts socket syscalls to redirect network traffic to the assigned IP. Two backends are available:

Preload (macOS + Linux)

A shared library (libsilo_bind) is injected via DYLD_INSERT_LIBRARIES (macOS) or LD_PRELOAD (Linux). It interposes on these syscalls:

bind()      0.0.0.0:port → SILO_IP:port
            127.0.0.1:port → SILO_IP:port

connect()   127.0.0.1:port → SILO_IP:port
            (only if a listener exists on SILO_IP:port)

sendto()    same rewriting as bind
sendmsg()   same rewriting as bind

getifaddrs()  hides other silo IPs from the interface list

The connect() rewrite probes for a listener first. If nothing is bound on SILO_IP:port, the connection goes to 127.0.0.1 as usual. This prevents hijacking connections to databases or other services running on localhost. You can disable connect rewriting entirely by setting SILO_CONNECT=0.

eBPF (Linux only)

On Linux, silo can use cgroup-attached BPF programs to intercept at the kernel level. This works with static binaries and doesn't require LD_PRELOAD.

# One-time setup (requires root)
sudo silo setup-ebpf

# After this, silo automatically uses the eBPF backend
silo ./my-static-binary

Requires cgroup v2 and kernel 5.8+.

IPv6 handling

When an app binds to :: (IPv6 any) or ::1 (IPv6 loopback), silo rewrites it to an IPv4-mapped IPv6 address: ::ffff:127.x.y.z. On macOS, silo replaces the IPv6 socket with an IPv4 socket entirely, copying all socket options.

macOS SIP bypass

macOS System Integrity Protection strips DYLD_INSERT_LIBRARIES from binaries in /usr/bin, /bin, etc. Silo handles this by:

1. Detecting SIP-protected paths
2. Searching PATH for a non-SIP alternative (e.g. Homebrew)
3. Following shebangs recursively (up to 5 levels)
4. Resolving /usr/bin/env -S invocations

For example, /bin/bash gets resolved to /opt/homebrew/bin/bash if available.

Limitations

Static binaries
Preload can't intercept statically linked binaries. Use eBPF on Linux, or CGO_ENABLED=1 for Go.
Non-git directories
Silo needs a git repo to compute the IP. Use --ip for non-git dirs.
macOS SIP
System binaries strip DYLD_INSERT_LIBRARIES. Install shells via Homebrew.
IPv4 only
Silo assigns IPs in 127.0.0.0/8. Pure IPv6 listeners are converted to IPv4-mapped addresses.