65bd47f342
Adds localnet testing mode to gateway-probe for LP development: - Add TestMode enum for different probe configurations - Add --gateway-ip flag for direct gateway testing - Implement two-hop WireGuard tunnel for localnet - Add mock ecash support for testing without real credentials - Add netstack Go bindings for userspace networking - Restructure probe with mode and common modules - Update README with localnet mode documentation
646 lines
16 KiB
Markdown
646 lines
16 KiB
Markdown
# Nym Localnet for Kata Container Runtimes
|
|
|
|
A complete Nym mixnet test environment running on Apple's container runtime for macOS (for now).
|
|
|
|
## Overview
|
|
|
|
This localnet setup provides a fully functional Nym mixnet for local development and testing:
|
|
- **3 mixnodes** (layer 1, 2, 3)
|
|
- **1 gateway** (entry + exit mode)
|
|
- **1 network-requester** (service provider)
|
|
- **1 SOCKS5 client**
|
|
|
|
All components run in isolated containers with proper networking and dynamic IP resolution.
|
|
|
|
## Prerequisites
|
|
|
|
### Required
|
|
- **macOS** (tested on macOS Sequoia 15.0+)
|
|
- **Apple Container Runtime** - Built into macOS
|
|
- **Docker Desktop** (for building images only)
|
|
- **Python 3** with `base58` library
|
|
|
|
### Installation
|
|
```bash
|
|
# Install Python dependencies
|
|
pip3 install --break-system-packages base58
|
|
|
|
# Verify container runtime is available
|
|
container --version
|
|
|
|
# Verify Docker is installed (for building)
|
|
docker --version
|
|
```
|
|
|
|
## Quick Start
|
|
|
|
```bash
|
|
# Navigate to the localnet directory
|
|
cd docker/localnet
|
|
|
|
# Build the container image
|
|
./localnet.sh build
|
|
|
|
# Start the localnet
|
|
./localnet.sh start
|
|
|
|
# Test the SOCKS5 proxy
|
|
curl -L --socks5 localhost:1080 https://nymtech.net
|
|
|
|
# View logs
|
|
./localnet.sh logs gateway
|
|
./localnet.sh logs socks5
|
|
|
|
# Stop the localnet
|
|
./localnet.sh stop
|
|
|
|
# Clean up everything
|
|
./localnet.sh clean
|
|
```
|
|
|
|
## Architecture
|
|
|
|
### Container Network
|
|
|
|
All containers run on a custom bridge network (`nym-localnet-network`) with dynamic IP assignment:
|
|
|
|
```
|
|
Host Machine (macOS)
|
|
├── nym-localnet-network (bridge)
|
|
│ ├── nym-mixnode1 (192.168.66.3)
|
|
│ ├── nym-mixnode2 (192.168.66.4)
|
|
│ ├── nym-mixnode3 (192.168.66.5)
|
|
│ ├── nym-gateway (192.168.66.6)
|
|
│ ├── nym-network-requester (192.168.66.7)
|
|
│ └── nym-socks5-client (192.168.66.8)
|
|
```
|
|
|
|
Ports published to host:
|
|
- 1080 → SOCKS5 proxy
|
|
- 9000/9001 → Gateway entry ports
|
|
- 10001-10005 → Mixnet ports
|
|
- 20001-20005 → Verloc ports
|
|
- 30001-30005 → HTTP APIs
|
|
- 41264/41265 → LP control ports (registration)
|
|
- 51822/51823 → WireGuard tunnel ports (gateway/gateway2)
|
|
|
|
### Startup Flow
|
|
|
|
1. **Container Initialization** (parallel)
|
|
- Each container starts and gets a dynamic IP
|
|
- Each node runs `nym-node run --init-only` with its container IP
|
|
- Bonding JSON files are written to shared volume
|
|
|
|
2. **Topology Generation** (sequential)
|
|
- Wait for all 4 bonding JSON files
|
|
- Get container IPs dynamically
|
|
- Run `build_topology.py` with container IPs
|
|
- Generate `network.json` with correct addresses
|
|
|
|
3. **Node Startup** (parallel)
|
|
- Each container starts its node with `--local` flag
|
|
- Nodes read configuration from init phase
|
|
- Clients use custom topology file
|
|
|
|
4. **Service Providers** (sequential)
|
|
- Network requester initializes and starts
|
|
- SOCKS5 client initializes with requester address
|
|
|
|
### Network Topology
|
|
|
|
The `network.json` file contains the complete network topology:
|
|
|
|
```json
|
|
{
|
|
"metadata": {
|
|
"key_rotation_id": 0,
|
|
"absolute_epoch_id": 0,
|
|
"refreshed_at": "2025-11-03T..."
|
|
},
|
|
"rewarded_set": {
|
|
"epoch_id": 0,
|
|
"entry_gateways": [4],
|
|
"exit_gateways": [4],
|
|
"layer1": [1],
|
|
"layer2": [2],
|
|
"layer3": [3],
|
|
"standby": []
|
|
},
|
|
"node_details": {
|
|
"1": { "mix_host": "192.168.66.3:10001", ... },
|
|
"2": { "mix_host": "192.168.66.4:10002", ... },
|
|
"3": { "mix_host": "192.168.66.5:10003", ... },
|
|
"4": { "mix_host": "192.168.66.6:10004", ... }
|
|
}
|
|
}
|
|
```
|
|
|
|
## Commands
|
|
|
|
### Build
|
|
```bash
|
|
./localnet.sh build
|
|
```
|
|
Builds the Docker image and loads it into Apple container runtime.
|
|
|
|
**Note**: First build takes ~5-10 minutes to compile all components.
|
|
|
|
### Start
|
|
```bash
|
|
./localnet.sh start
|
|
```
|
|
Starts all containers, generates topology, and launches the complete network.
|
|
|
|
**Expected output**:
|
|
```
|
|
[INFO] Starting Nym Localnet...
|
|
[SUCCESS] Network created: nym-localnet-network
|
|
[INFO] Starting nym-mixnode1...
|
|
[SUCCESS] nym-mixnode1 started
|
|
...
|
|
[INFO] Building network topology with container IPs...
|
|
[SUCCESS] Network topology created successfully
|
|
[SUCCESS] Nym Localnet is running!
|
|
|
|
Test with:
|
|
curl -x socks5h://127.0.0.1:1080 https://nymtech.net
|
|
```
|
|
|
|
### Stop
|
|
```bash
|
|
./localnet.sh stop
|
|
```
|
|
Stops and removes all running containers.
|
|
|
|
### Clean
|
|
```bash
|
|
./localnet.sh clean
|
|
```
|
|
Complete cleanup: removes containers, volumes, network, and temporary files.
|
|
|
|
### Logs
|
|
```bash
|
|
# View logs for a specific container
|
|
./localnet.sh logs <container-name>
|
|
|
|
# Container names:
|
|
# - mix1, mix2, mix3
|
|
# - gateway
|
|
# - requester
|
|
# - socks5
|
|
|
|
# Examples:
|
|
./localnet.sh logs gateway
|
|
./localnet.sh logs socks5
|
|
container logs nym-gateway --follow
|
|
```
|
|
|
|
### Status
|
|
```bash
|
|
# List all containers
|
|
container list
|
|
|
|
# Check specific container
|
|
container logs nym-gateway
|
|
|
|
# Inspect network
|
|
container network inspect nym-localnet-network
|
|
```
|
|
|
|
## Testing
|
|
|
|
### Basic SOCKS5 Test
|
|
```bash
|
|
# Simple HTTP request with redirect following
|
|
curl -L --socks5 localhost:1080 http://example.com
|
|
|
|
# HTTPS request
|
|
curl -L --socks5 localhost:1080 https://nymtech.net
|
|
|
|
# Download a file
|
|
curl -L --socks5 localhost:1080 \
|
|
https://test-download-files-nym.s3.amazonaws.com/download-files/1MB.zip \
|
|
--output /tmp/test.zip
|
|
```
|
|
|
|
### Verify Network Topology
|
|
```bash
|
|
# View the generated topology
|
|
container exec nym-gateway cat /localnet/network.json | jq .
|
|
|
|
# Check container IPs
|
|
container list | grep nym-
|
|
|
|
# Verify all bonding files exist
|
|
container exec nym-gateway ls -la /localnet/
|
|
```
|
|
|
|
### Test Mixnet Routing
|
|
```bash
|
|
# All traffic flows through: client → mix1 → mix2 → mix3 → gateway → internet
|
|
# Watch logs to verify routing:
|
|
container logs nym-mixnode1 --follow &
|
|
container logs nym-mixnode2 --follow &
|
|
container logs nym-mixnode3 --follow &
|
|
container logs nym-gateway --follow &
|
|
|
|
# Make a request
|
|
curl -L --socks5 localhost:1080 https://nymtech.com
|
|
```
|
|
|
|
### LP (Lewes Protocol) Testing
|
|
|
|
The gateway is configured with LP listener enabled and **mock ecash verification** for testing:
|
|
|
|
```bash
|
|
# LP listener ports (exposed on host):
|
|
# - 41264: LP control port (TCP registration)
|
|
# - 51264: LP data port
|
|
|
|
# Check LP ports are listening
|
|
nc -zv localhost 41264
|
|
nc -zv localhost 51264
|
|
|
|
# Test LP registration with nym-gateway-probe
|
|
cargo run -p nym-gateway-probe run-local \
|
|
--mnemonic "test mnemonic here" \
|
|
--gateway-ip 'localhost:41264' \
|
|
--only-lp-registration
|
|
```
|
|
|
|
**Mock Ecash Mode**:
|
|
- Gateway uses `--lp.use-mock-ecash true` flag
|
|
- Accepts ANY bandwidth credential without blockchain verification
|
|
- Perfect for testing LP protocol implementation
|
|
- **WARNING**: Never use mock ecash in production!
|
|
|
|
**Testing without blockchain**:
|
|
The mock ecash manager allows testing the complete LP registration flow without requiring:
|
|
- Running nyxd blockchain
|
|
- Deploying smart contracts
|
|
- Acquiring real bandwidth credentials
|
|
- Setting up coconut signers
|
|
|
|
This makes localnet perfect for rapid LP protocol development and testing.
|
|
|
|
## File Structure
|
|
|
|
```
|
|
docker/localnet/
|
|
├── README.md # This file
|
|
├── localnet.sh # Main orchestration script
|
|
├── Dockerfile.localnet # Docker image definition
|
|
└── build_topology.py # Topology generator
|
|
```
|
|
|
|
## How It Works
|
|
|
|
### Node Initialization
|
|
|
|
Each node initializes itself at runtime inside its container:
|
|
|
|
```bash
|
|
# Get container IP
|
|
CONTAINER_IP=$(hostname -i)
|
|
|
|
# Initialize with container IP
|
|
nym-node run --id mix1-localnet --init-only \
|
|
--unsafe-disable-replay-protection \
|
|
--local \
|
|
--mixnet-bind-address=0.0.0.0:10001 \
|
|
--verloc-bind-address=0.0.0.0:20001 \
|
|
--http-bind-address=0.0.0.0:30001 \
|
|
--http-access-token=lala \
|
|
--public-ips $CONTAINER_IP \
|
|
--output=json \
|
|
--bonding-information-output="/localnet/mix1.json"
|
|
```
|
|
|
|
**Key flags**:
|
|
- `--local`: Accept private IPs for local development
|
|
- `--public-ips`: Announce the container's IP address
|
|
- `--unsafe-disable-replay-protection`: Disable bloomfilter to save memory
|
|
|
|
### Dynamic Topology
|
|
|
|
The topology is built **after** containers start:
|
|
|
|
```bash
|
|
# Get container IPs
|
|
MIX1_IP=$(container exec nym-mixnode1 hostname -i)
|
|
MIX2_IP=$(container exec nym-mixnode2 hostname -i)
|
|
MIX3_IP=$(container exec nym-mixnode3 hostname -i)
|
|
GATEWAY_IP=$(container exec nym-gateway hostname -i)
|
|
|
|
# Build topology with actual IPs
|
|
python3 build_topology.py /localnet localnet \
|
|
$MIX1_IP $MIX2_IP $MIX3_IP $GATEWAY_IP
|
|
```
|
|
|
|
This ensures the topology contains reachable container addresses.
|
|
|
|
### Client Configuration
|
|
|
|
Clients use `--custom-mixnet` to read the local topology:
|
|
|
|
```bash
|
|
# Network requester
|
|
nym-network-requester init \
|
|
--id "network-requester-$SUFFIX" \
|
|
--open-proxy=true \
|
|
--custom-mixnet /localnet/network.json
|
|
|
|
# SOCKS5 client
|
|
nym-socks5-client init \
|
|
--id "socks5-client-$SUFFIX" \
|
|
--provider "$REQUESTER_ADDRESS" \
|
|
--custom-mixnet /localnet/network.json \
|
|
--host 0.0.0.0
|
|
```
|
|
|
|
The `--custom-mixnet` flag tells clients to use our local topology instead of fetching from nym-api.
|
|
|
|
## Troubleshooting
|
|
|
|
### Container Build Issues
|
|
|
|
**Problem**: Docker build fails
|
|
```bash
|
|
# Check Docker is running
|
|
docker info
|
|
|
|
# Clean Docker cache
|
|
docker system prune -a
|
|
|
|
# Rebuild with no cache
|
|
./localnet.sh build
|
|
```
|
|
|
|
**Problem**: Container image load fails
|
|
```bash
|
|
# Verify temp file was created
|
|
ls -lh /tmp/nym-localnet-image-*
|
|
|
|
# Check container runtime
|
|
container image list
|
|
|
|
# Manually load if needed
|
|
docker save -o /tmp/nym-image.tar nym-localnet:latest
|
|
container image load --input /tmp/nym-image.tar
|
|
```
|
|
|
|
### Network Issues
|
|
|
|
**Problem**: Containers can't communicate
|
|
```bash
|
|
# Check network exists
|
|
container network list | grep nym-localnet
|
|
|
|
# Inspect network
|
|
container network inspect nym-localnet-network
|
|
|
|
# Verify containers are on the network
|
|
container list | grep nym-
|
|
```
|
|
|
|
**Problem**: SOCKS5 connection refused
|
|
```bash
|
|
# Check SOCKS5 is listening
|
|
container logs nym-socks5-client | grep "Listening on"
|
|
|
|
# Verify port mapping
|
|
container list | grep socks5
|
|
|
|
# Test from host
|
|
nc -zv localhost 1080
|
|
```
|
|
|
|
### Node Issues
|
|
|
|
**Problem**: "No valid public addresses" error
|
|
- Ensure `--local` flag is present in both init and run commands
|
|
- Check container can resolve its own IP: `container exec nym-mixnode1 hostname -i`
|
|
- Verify `--public-ips` is using `$CONTAINER_IP` variable
|
|
|
|
**Problem**: "TUN device error"
|
|
- The gateway needs TUN device support for exit functionality
|
|
- Verify `iproute2` is installed in the image (adds `ip` command)
|
|
- Check gateway logs: `container logs nym-gateway`
|
|
- The gateway should show: "Created TUN device: nymtun0"
|
|
|
|
**Problem**: "Noise handshake" warnings
|
|
- These are warnings, not errors - nodes fall back to TCP
|
|
- Does not affect functionality in local development
|
|
- Safe to ignore for testing purposes
|
|
|
|
### Topology Issues
|
|
|
|
**Problem**: Network.json not created
|
|
```bash
|
|
# Check all bonding files exist
|
|
container exec nym-gateway ls -la /localnet/
|
|
|
|
# Verify build_topology.py ran
|
|
container logs nym-gateway | grep "Building network topology"
|
|
|
|
# Check Python dependencies
|
|
container exec nym-gateway python3 -c "import base58"
|
|
```
|
|
|
|
**Problem**: Clients can't connect to nodes
|
|
```bash
|
|
# Verify IPs in topology match container IPs
|
|
container exec nym-gateway cat /localnet/network.json | jq '.node_details'
|
|
container list | grep nym-
|
|
|
|
# Check containers can reach each other
|
|
container exec nym-socks5-client ping -c 1 192.168.66.6
|
|
```
|
|
|
|
### Startup Issues
|
|
|
|
**Problem**: Containers exit immediately
|
|
```bash
|
|
# Check logs for errors
|
|
container logs nym-mixnode1
|
|
|
|
# Common issues:
|
|
# - Missing network.json: Wait for topology to be built
|
|
# - Port already in use: Check for conflicting services
|
|
# - Init failed: Check for correct container IP
|
|
```
|
|
|
|
**Problem**: Topology build times out
|
|
```bash
|
|
# Verify all containers initialized
|
|
container exec nym-gateway ls -la /localnet/*.json
|
|
|
|
# Check for init errors
|
|
container logs nym-mixnode1 | grep -i error
|
|
|
|
# Manual cleanup and restart
|
|
./localnet.sh clean
|
|
./localnet.sh start
|
|
```
|
|
|
|
## Performance Notes
|
|
|
|
### Memory Usage
|
|
- Each mixnode: ~200MB
|
|
- Gateway: ~300MB (includes TUN device)
|
|
- Network requester: ~150MB
|
|
- SOCKS5 client: ~150MB
|
|
- **Total**: ~1.2GB + overhead
|
|
|
|
**Recommended**: 4GB+ system memory
|
|
|
|
### Startup Time
|
|
- Image build: ~5-10 minutes (first time)
|
|
- Network start: ~20-30 seconds
|
|
- Node initialization: ~5-10 seconds per node (parallel)
|
|
|
|
### Latency
|
|
Mixnet adds latency by design for privacy:
|
|
- ~1-3 seconds for SOCKS5 requests
|
|
- Cover traffic adds random delays
|
|
- Local testing may show variable timing
|
|
|
|
This is **expected behavior** - the mixnet provides privacy through traffic mixing.
|
|
|
|
## Advanced Configuration
|
|
|
|
### Custom Node Configuration
|
|
|
|
Edit node init commands in `localnet.sh` (search for `nym-node run --init-only`):
|
|
|
|
```bash
|
|
# Example: Change mixnode ports
|
|
--mixnet-bind-address=0.0.0.0:11001 \
|
|
--verloc-bind-address=0.0.0.0:21001 \
|
|
--http-bind-address=0.0.0.0:31001 \
|
|
```
|
|
|
|
Remember to update port mappings in the `container run` command as well.
|
|
|
|
### Enable Replay Protection
|
|
|
|
Remove `--unsafe-disable-replay-protection` flags (requires more memory):
|
|
|
|
```bash
|
|
# In start_mixnode() and start_gateway() functions
|
|
nym-node run --id mix1-localnet --init-only \
|
|
--local \
|
|
--mixnet-bind-address=0.0.0.0:10001 \
|
|
# ... other flags (without --unsafe-disable-replay-protection)
|
|
```
|
|
|
|
**Note**: Each node will require an additional ~1.5GB memory for bloomfilter.
|
|
|
|
### API Access
|
|
|
|
Each node exposes an HTTP API:
|
|
|
|
```bash
|
|
# Get gateway info
|
|
curl -H "Authorization: Bearer lala" http://localhost:30004/api/v1/gateway
|
|
|
|
# Get mixnode stats
|
|
curl -H "Authorization: Bearer lala" http://localhost:30001/api/v1/stats
|
|
|
|
# Get node description
|
|
curl -H "Authorization: Bearer lala" http://localhost:30001/api/v1/description
|
|
```
|
|
|
|
Access token is `lala` (configured with `--http-access-token=lala`).
|
|
|
|
### Add More Mixnodes
|
|
|
|
To add a 4th mixnode:
|
|
|
|
1. **Update constants** in `localnet.sh`:
|
|
```bash
|
|
MIXNODE4_CONTAINER="nym-mixnode4"
|
|
```
|
|
|
|
2. **Add start call** in `start_all()`:
|
|
```bash
|
|
start_mixnode 4 "$MIXNODE4_CONTAINER"
|
|
```
|
|
|
|
3. **Update topology builder** to include the new node
|
|
|
|
4. **Rebuild and restart**:
|
|
```bash
|
|
./localnet.sh clean
|
|
./localnet.sh build
|
|
./localnet.sh start
|
|
```
|
|
|
|
## Technical Details
|
|
|
|
### Container Runtime
|
|
|
|
Apple's container runtime is a native macOS container system:
|
|
- Uses Virtualization.framework for isolation
|
|
- Lightweight VMs for each container
|
|
- Native macOS integration
|
|
- Separate image store from Docker
|
|
- Natively uses [Kata Containers](https://github.com/kata-containers/kata-containers) images
|
|
|
|
### Initial setup for [Container Runtime](https://github.com/apple/container)
|
|
|
|
- **MUST** have MacOS Tahoe for inter-container networking
|
|
- `brew install --cask container`
|
|
- Download Kata Containers 3.20, this one can be loaded by `container` and has `CONFIG_TUN=y` kernel flag
|
|
- `https://github.com/kata-containers/kata-containers/releases/download/3.20.0/kata-static-3.20.0-arm64.tar.xz`
|
|
- Load new kernel
|
|
- `container system kernel set --tar kata-static-3.20.0-arm64.tar.xz --binary opt/kata/share/kata-containers/vmlinux-6.12.42-162`
|
|
- Validate kernel version once you have container running
|
|
- `uname -r` should return `6.12.42`
|
|
- `cat /proc/config.gz | grep CONFIG_TUN` should return `CONFIG_TUN=y`
|
|
|
|
### Image Building
|
|
|
|
Images are built with Docker then transferred:
|
|
1. `docker build` creates the image
|
|
2. `docker save` exports to tar file
|
|
3. `container image load` imports into container runtime
|
|
4. Temporary file is cleaned up
|
|
|
|
This approach allows using Docker's build cache while running on Apple's runtime.
|
|
|
|
### Network Architecture
|
|
|
|
The custom bridge network (`nym-localnet-network`):
|
|
- Provides container-to-container communication
|
|
- Assigns dynamic IPs from 192.168.66.0/24
|
|
- NAT for outbound internet access
|
|
- Port publishing for host access
|
|
|
|
### Volumes
|
|
|
|
Two types of volumes:
|
|
1. **Shared data** (`/tmp/nym-localnet-*`): Bonding files and topology
|
|
2. **Node configs** (`/tmp/nym-localnet-home-*`): Node configurations
|
|
|
|
Both are ephemeral by default (cleaned up on stop).
|
|
|
|
## Known Limitations
|
|
|
|
- **macOS only**: Apple container runtime requires macOS
|
|
- **No Docker Compose**: Uses custom orchestration script
|
|
- **Dynamic IPs**: Container IPs may change between restarts
|
|
- **Port conflicts**: Cannot run alongside services using same ports
|
|
- **TUN device**: Gateway requires `ip` command for network interfaces
|
|
|
|
## Support
|
|
|
|
For issues and questions:
|
|
- **GitHub Issues**: https://github.com/nymtech/nym/issues
|
|
- **Documentation**: https://nymtech.net/docs
|
|
- **Discord**: https://discord.gg/nym
|
|
|
|
## License
|
|
|
|
This localnet setup is part of the Nym project and follows the same license.
|