Overview

WolfNet creates a secure private network across the internet. Connect servers across data centres, cloud providers, and on-premises infrastructure as if they were on the same local network. Every packet is encrypted end-to-end with WireGuard-class cryptography.

WolfNet works seamlessly whether your peers are on the same LAN, behind NAT, or spread across continents. LAN peers are discovered automatically, remote peers connect via public IP or DynDNS hostname, and peers behind restrictive firewalls reach each other through relay forwarding — all without any manual port forwarding.

Key Features

  • Encrypted mesh networking — X25519 + ChaCha20-Poly1305 (WireGuard-class)
  • Invite/Join — Connect peers with a single token, no manual config
  • LAN auto-discovery — Peers on the same network find each other automatically
  • DynDNS support — Remote access via dynamic DNS hostnames, re-resolved every 60 seconds
  • Relay forwarding — No port forwarding needed — peers relay traffic automatically
  • Gateway mode — Route internet traffic through a WolfNet peer with NAT
  • Peer exchange (PEX) — Nodes share their peer lists so the mesh self-heals
  • Container integration — Docker and LXC containers get their own WolfNet addresses
  • TUN-based — Layer 3 networking with automatic route management
  • Built-in VPN — Access your entire infrastructure from anywhere
  • Docker container — Run on Unraid, Synology, TrueNAS, or any Docker host — no full install needed
  • IBM Power ready — Pure Rust, builds natively on ppc64le

How It Works

WolfNet creates a TUN virtual network interface (wolfnet0) on each node and assigns it an IP address on the 10.10.10.0/24 private subnet. All traffic between peers is encrypted end-to-end using modern cryptography.

Your Applications
SSH, HTTP, databases, WolfStack UI
wolfnet0 (10.10.10.x/24)
TUN virtual interface
WolfNet Daemon
Peer ManagerDiscovery, handshake, keepalive
EncryptionX25519 + ChaCha20-Poly1305
Routing EngineDirect, relay, gateway, PEX, container routes
UDP Transport (port 9600)
LAN broadcast discovery (port 9601) • Peer exchange (PEX)

Encryption

Every WolfNet node generates an X25519 keypair on first run. When two peers connect, they perform a Diffie-Hellman key exchange to derive a shared secret used as the ChaCha20-Poly1305 encryption key. This is the same cryptographic foundation used by WireGuard and TLS 1.3.

  • Key exchange — X25519 (Curve25519 Diffie-Hellman)
  • Encryption — ChaCha20-Poly1305 AEAD (authenticated encryption)
  • Peer identity — SHA-256 derived 4-byte peer IDs for packet routing
  • Replay protection — Monotonic nonce counters with direction flags
  • Key storage — Private key at /etc/wolfnet/private.key (mode 0600)

Packet Flow

  1. Application sends traffic to a 10.10.10.x address
  2. The kernel routes it through the wolfnet0 TUN interface
  3. WolfNet reads the packet, looks up the destination peer
  4. The packet is encrypted with the peer’s shared ChaCha20-Poly1305 key
  5. The encrypted packet is sent as a UDP datagram to the peer’s real endpoint
  6. The receiving peer decrypts and writes the packet to its local TUN interface
  7. The kernel delivers the packet to the destination application

Installation

WolfNet is included with WolfStack — if you have WolfStack installed, you already have WolfNet. For standalone installation on machines without WolfStack:

bash
curl -sSL https://raw.githubusercontent.com/wolfsoftwaresystemsltd/WolfNet/main/setup.sh | sudo bash

The installer compiles WolfNet from source using Rust, creates the systemd service, and generates a default configuration.

Manual Build

bash
git clone https://github.com/wolfsoftwaresystemsltd/WolfScale.git
cd WolfScale/wolfnet
cargo build --release
sudo cp target/release/wolfnet /usr/local/bin/
sudo cp target/release/wolfnetctl /usr/local/bin/

Requirements

  • Linux with TUN device support (/dev/net/tun)
  • Root privileges (creates network interfaces)
  • UDP port 9600 open for tunnel traffic
  • UDP port 9601 open for LAN discovery (optional)

Docker Container

Run WolfNet on systems where you can’t install the full WolfStack binary — Unraid, Synology, TrueNAS, or any Docker host. The container runs in host networking mode and creates an encrypted mesh tunnel just like the native install.

Quick Start

Get an invite token from an existing WolfNet node (wolfnet invite or via the WolfStack UI), then:

bash
docker run -d --name wolfnet \
  --network host \
  --cap-add NET_ADMIN \
  --device /dev/net/tun:/dev/net/tun \
  -v wolfnet-config:/etc/wolfnet \
  -e WOLFNET_JOIN_TOKEN=<your-invite-token> \
  ghcr.io/wolfsoftwaresystemsltd/wolfnet:latest

Docker Compose

yaml
services:
  wolfnet:
    image: ghcr.io/wolfsoftwaresystemsltd/wolfnet:latest
    container_name: wolfnet
    restart: unless-stopped
    network_mode: host
    cap_add:
      - NET_ADMIN
    devices:
      - /dev/net/tun:/dev/net/tun
    volumes:
      - wolfnet-config:/etc/wolfnet
    environment:
      - WOLFNET_JOIN_TOKEN=${WOLFNET_JOIN_TOKEN}

volumes:
  wolfnet-config:

Run with: WOLFNET_JOIN_TOKEN=<token> docker compose up -d

How It Works

  • Host networking — The container shares the host’s network namespace so the TUN interface is visible on the host
  • No kernel modules — WolfNet uses userspace cryptography (X25519 + ChaCha20-Poly1305), not WireGuard kernel modules. Only /dev/net/tun is needed
  • Auto key generation — On first run, the entrypoint generates a key pair and saves it to the config volume
  • Join token — Pass WOLFNET_JOIN_TOKEN on first run to automatically join an existing mesh. The token is only used once
  • Persistent config — The /etc/wolfnet volume stores your private key and config across container restarts

Environment Variables

VariableDefaultDescription
WOLFNET_JOIN_TOKENInvite token from an existing node
WOLFNET_ADDRESSautoOverride the auto-assigned WolfNet IP
WOLFNET_PORT9600UDP port for tunnel traffic
WOLFNET_GATEWAYfalseEnable NAT gateway mode
WOLFNET_DISCOVERYtrueEnable LAN broadcast peer discovery
WOLFNET_INTERFACEwolfnet0TUN interface name
WOLFNET_HOSTNAMEcontainer hostnameHostname reported to peers

Platform Notes

PlatformStatusNotes
UnraidFully supportedUnraid template available. Config at /mnt/user/appdata/wolfnet
Synology DSMSupportedMay need insmod /lib/modules/tun.ko if TUN not loaded
TrueNAS ScaleSupportedDebian-based, TUN available by default
Generic LinuxFully supportedAny Docker host with /dev/net/tun

Satellite Node (Full NAS Integration)

Combine WolfNet with WolfDisk to join your NAS to the WolfStack cluster with both networking and distributed storage:

yaml
services:
  wolfnet:
    image: ghcr.io/wolfsoftwaresystemsltd/wolfnet:latest
    restart: unless-stopped
    network_mode: host
    cap_add: [NET_ADMIN]
    devices: ["/dev/net/tun:/dev/net/tun"]
    volumes: [wolfnet-config:/etc/wolfnet]
    environment:
      - WOLFNET_JOIN_TOKEN=${WOLFNET_JOIN_TOKEN}

  wolfdisk:
    image: ghcr.io/wolfsoftwaresystemsltd/wolfdisk:latest
    restart: unless-stopped
    privileged: true
    network_mode: host
    devices: ["/dev/fuse:/dev/fuse"]
    volumes:
      - wolfdisk-config:/etc/wolfdisk
      - wolfdisk-data:/var/lib/wolfdisk
      - /mnt/wolfdisk:/mnt/wolfdisk:rshared
    environment:
      - WOLFDISK_ROLE=client
      - WOLFDISK_PEERS=${WOLFDISK_LEADER}
    depends_on: [wolfnet]

volumes:
  wolfnet-config:
  wolfdisk-config:
  wolfdisk-data:

Quick Start

1. Initialize the first node

bash
sudo wolfnet init --address 10.10.10.1

This creates /etc/wolfnet/config.toml and generates an X25519 keypair.

2. Start WolfNet

bash
sudo systemctl start wolfnet
sudo systemctl enable wolfnet

3. Generate an invite token

bash
sudo wolfnet --config /etc/wolfnet/config.toml invite

This outputs a token containing your public key, endpoint, and network details. Share it with the machine you want to connect.

4. Join from a second node

bash
# Install WolfNet on the second machine, then:
sudo wolfnet --config /etc/wolfnet/config.toml join <token>

The joining node is automatically assigned the next available IP address (e.g. 10.10.10.2) and added as a peer. It also prints a reverse token — run that command on the first node so both sides know about each other.

5. Restart both nodes

bash
sudo systemctl restart wolfnet

Both peers are now connected with encrypted tunnels. Test with:

bash
ping 10.10.10.2          # From node 1
wolfnetctl peers         # View connected peers
wolfnetctl status        # View this node's status

💡 WolfStack users: You can also generate invite tokens and add peers from the WolfStack dashboard under Networking → WolfNet, or use the cluster settings page to automatically sync WolfNet connections across all cluster nodes with one click.

Configuration

WolfNet is configured via /etc/wolfnet/config.toml. The wolfnet init command or WolfStack installer creates this file for you.

toml
# WolfNet Configuration

[network]
interface = "wolfnet0"        # TUN interface name
address = "10.10.10.1"        # This node's WolfNet IP address
subnet = 24                   # Subnet mask (CIDR)
listen_port = 9600            # UDP port for tunnel traffic
gateway = false               # Enable gateway/NAT mode
discovery = true              # Enable LAN auto-discovery broadcasts
mtu = 1400                    # TUN interface MTU

[security]
private_key_file = "/etc/wolfnet/private.key"

# Add peers — one [[peers]] block per remote node
[[peers]]
public_key = "base64_encoded_x25519_public_key"
endpoint = "203.0.113.5:9600"        # IP:port or hostname:port
allowed_ip = "10.10.10.2"            # Peer's WolfNet address
name = "server-2"                     # Friendly name (optional)

[[peers]]
public_key = "another_base64_key"
endpoint = "home.example.com:9600"   # DynDNS hostname supported
allowed_ip = "10.10.10.3"
name = "home-server"

Configuration Reference

Section Key Default Description
[network] interface wolfnet0 Name of the TUN interface created by WolfNet
address This node’s WolfNet IP address (e.g. 10.10.10.1)
subnet 24 Subnet mask in CIDR notation
listen_port 9600 UDP port for encrypted tunnel traffic
gateway false Enable gateway mode (NAT for internet access)
discovery true Enable UDP broadcast for LAN auto-discovery
mtu 1400 Maximum transmission unit for the TUN interface
[security] private_key_file /etc/wolfnet/private.key Path to the X25519 private key file
[[peers]] public_key Peer’s base64-encoded X25519 public key (required)
endpoint Peer’s IP:port or hostname:port (optional if using discovery)
allowed_ip Peer’s WolfNet IP address (required)
name Friendly name for identification (optional)

Peer Discovery

WolfNet uses multiple methods to find and connect to peers, allowing it to work across any network topology.

LAN Auto-Discovery

When discovery = true (the default), WolfNet broadcasts a UDP announcement every 2 seconds on port 9601. Any other WolfNet node on the same LAN segment will automatically detect it and establish a connection — no configuration needed.

This means if you install WolfNet on two servers on the same network and run wolfnet init on each, they will find each other automatically.

Static Endpoints

For peers on different networks, specify their public IP address or hostname in the endpoint field:

toml
[[peers]]
public_key = "abc123..."
endpoint = "203.0.113.5:9600"    # Static public IP
allowed_ip = "10.10.10.2"
name = "cloud-server"

DynDNS Hostnames

If a peer has a dynamic IP address (e.g. a home server), use a DynDNS hostname instead. WolfNet re-resolves hostnames every 60 seconds, so if the IP changes, the connection recovers automatically:

toml
[[peers]]
public_key = "xyz789..."
endpoint = "myserver.duckdns.org:9600"    # DynDNS hostname
allowed_ip = "10.10.10.3"
name = "home-lab"

💡 Tip: Free DynDNS services like DuckDNS or No-IP work perfectly with WolfNet. Set up the DynDNS client on your router or server, then use the hostname in your WolfNet config.

Peer Exchange (PEX)

Every 30 seconds, each WolfNet node shares its full peer list with all connected peers. This allows the mesh to self-heal — if node A knows node B and node C, but B and C don’t know each other, A will introduce them via PEX. If direct connection isn’t possible, traffic is relayed through A.

Endpoint Roaming

WolfNet identifies peers by their cryptographic key, not by IP address. If a peer’s public IP changes (e.g. after a NAT rebind or ISP address change), WolfNet updates the endpoint mapping automatically when the next packet arrives. Combined with DynDNS re-resolution, this makes WolfNet resilient to IP address changes.

Remote Access Setup

One of WolfNet’s most powerful features is that the same configuration works for both local and remote access. A peer on the LAN connects via auto-discovery, and the same peer connecting from a coffee shop reaches your network via the configured endpoint — no config changes needed.

Scenario: Office + Home Server

You have a server at the office with a static IP and a server at home with a dynamic IP. You want both to be on the same WolfNet and accessible from anywhere.

Office Server (static IP: 203.0.113.5)

toml
[network]
address = "10.10.10.1"
listen_port = 9600
discovery = true         # Also discover LAN peers

[[peers]]
public_key = "home_server_public_key_base64"
endpoint = "home.duckdns.org:9600"
allowed_ip = "10.10.10.2"
name = "home-server"

Home Server (dynamic IP, DuckDNS: home.duckdns.org)

toml
[network]
address = "10.10.10.2"
listen_port = 9600
discovery = true

[[peers]]
public_key = "office_server_public_key_base64"
endpoint = "203.0.113.5:9600"
allowed_ip = "10.10.10.1"
name = "office-server"

Router Configuration

For the home server behind NAT, forward UDP port 9600 to the home server’s local IP in your router settings. Alternatively, if you can’t forward ports, the home server can still connect — the office server just needs to be reachable, and traffic will flow through the established connection.

💡 No port forwarding? If neither side can open ports, use a third node with a public IP as a relay. Traffic between the two will be automatically relayed through the third node via peer exchange. See Relay Forwarding below.

Scenario: Laptop VPN Access

Install WolfNet on your laptop and join the network. Whether you’re in the office (LAN discovery finds peers automatically) or working remotely (connects via the configured endpoint), you have the same access to all 10.10.10.x resources.

bash
# On any WolfStack node, generate an invite:
sudo wolfnet --config /etc/wolfnet/config.toml invite

# On your laptop:
sudo wolfnet --config /etc/wolfnet/config.toml join <token>
sudo systemctl restart wolfnet

# Now access everything:
ssh admin@10.10.10.1                     # SSH to a server
firefox https://10.10.10.1:8553          # WolfStack dashboard
mysql -h 10.10.10.2 -u root mydb        # Database on another node
curl http://10.10.10.100:8080            # App in a container

Scenario: Multi-Site Cluster

Connect servers across multiple data centres and cloud providers. Each site discovers local peers automatically, and cross-site connections use static endpoints or DynDNS:

Site A — London
10.10.10.1 (node-a)
10.10.10.2 (node-b)
LAN auto-discovery
WolfNet
tunnel
Site B — Frankfurt
10.10.10.3 (node-c)
10.10.10.4 (node-d)
LAN auto-discovery
VPN
access
Remote
10.10.10.5 (laptop)

Only one peer at each site needs a cross-site endpoint configured. Other peers at the same site are discovered via LAN and reached via relay or PEX.

Invite/Join System

The invite/join system is the easiest way to connect peers. An invite token encodes everything the joining node needs: the inviter’s public key, endpoint, WolfNet IP, subnet, and listen port.

How It Works

  1. Generate token: Run wolfnet invite on an existing node. WolfNet auto-detects your public IP and encodes it into the token.
  2. Join: Run wolfnet join <token> on the new node. It decodes the token, assigns itself the next available IP, and adds the inviter as a peer.
  3. Reverse token: The join command prints a reverse token. Run this on the original node to complete the bidirectional peering.
  4. Restart: Restart WolfNet on both nodes — they connect immediately.
bash
# Node A — generate invite
$ sudo wolfnet --config /etc/wolfnet/config.toml invite
Invite token:
  sudo wolfnet --config /etc/wolfnet/config.toml join eyJwa...

# Node B — join using the token
$ sudo wolfnet --config /etc/wolfnet/config.toml join eyJwa...
Joined network! This node is 10.10.10.2
Run this on the inviting node to complete the connection:
  sudo wolfnet --config /etc/wolfnet/config.toml join eyJxb...

# Node A — run the reverse token
$ sudo wolfnet --config /etc/wolfnet/config.toml join eyJxb...

# Both nodes — restart
$ sudo systemctl restart wolfnet

💡 WolfStack dashboard: In the WolfStack UI, go to Networking → WolfNet to generate invites, add peers, and manage connections graphically. The cluster settings page also has a Update WolfNet Connections button that automatically syncs WolfNet peers across all cluster nodes.

Relay Forwarding

Relay forwarding happens automatically — no configuration needed. When a peer cannot be reached directly (e.g. both sides are behind NAT), traffic is relayed through a mutually reachable peer.

How Relay Works

Node A10.10.10.1 — behind NAT
Node B10.10.10.2 — public IP
Node C10.10.10.3 — behind NAT
A cannot reach C directly — B relays automatically
1
A encrypts packet for B, sends to B
2
B decrypts, sees destination is C
3
B re-encrypts for C, forwards to C
4
C decrypts and receives the packet
  1. Node B shares its peer list with A and C via peer exchange (PEX)
  2. A learns about C (with relay_via = B) and C learns about A
  3. When A sends traffic to C, it routes through B automatically
  4. If A and C later establish a direct connection (e.g. via port forwarding), the relay is dropped in favour of the direct path

Any WolfNet node with two or more peers automatically enables IP forwarding and can act as a relay. The WolfStack dashboard shows relayed peers with a purple “Relay via” badge in the Global View.

Gateway Mode

Gateway mode turns a WolfNet node into a NAT gateway, allowing other peers to route their internet traffic through it. This is useful when you want to give peers internet access through a specific exit point.

Enable Gateway

Set gateway = true in the gateway node’s config:

toml
[network]
address = "10.10.10.1"
gateway = true           # Enable NAT gateway

You can also toggle this from the WolfStack dashboard under Networking → WolfNet.

What Gateway Mode Does

  • Detects the external network interface automatically
  • Enables IP forwarding (/proc/sys/net/ipv4/ip_forward)
  • Adds iptables MASQUERADE rules for NAT
  • Allows forwarding from WolfNet to the internet
  • Blocks inbound traffic from the internet to the WolfNet subnet
  • All rules are cleaned up automatically on shutdown

Client Configuration

On a client node that should use the gateway for internet access, add a default route via the gateway’s WolfNet IP:

bash
# Route all traffic through the gateway peer
sudo ip route add default via 10.10.10.1 dev wolfnet0

Container Integration

WolfStack can assign WolfNet IP addresses to Docker containers and LXC containers, making them directly addressable across the mesh network. Container IPs are allocated from the 10.10.10.100–254 range.

Docker Containers

When creating a Docker container in WolfStack, assign a WolfNet IP via the WolfNet IP field. This is stored as a Docker label:

bash
# WolfStack sets this automatically, but you can also do it manually:
docker create --name myapp --label wolfnet.ip=10.10.10.100 nginx

WolfStack then:

  1. Adds the WolfNet IP as an alias on the container’s network interface
  2. Adds a route for the WolfNet subnet inside the container
  3. Adds a host-side route so other WolfNet peers can reach the container
  4. Publishes the container route to remote peers via routes.json

The container is now reachable at 10.10.10.100 from any peer on the mesh.

LXC Containers

LXC containers use a marker file at /var/lib/lxc/<name>/.wolfnet/ip. WolfStack assigns the IP and configures routing automatically, including Proxmox LXC containers with dedicated WolfNet bridge interfaces.

Route Propagation

Container IP mappings are written to /var/run/wolfnet/routes.json. The WolfNet daemon reads this file and uses it to route traffic destined for containers on remote nodes to the correct host peer. Routes are propagated across the cluster automatically.

Platform Support

WolfNet is Linux only. It requires a Linux TUN device (/dev/net/tun) and runs as a root daemon. There are no clients for iOS, Android, Windows, or macOS.

Supported architectures: x86_64, aarch64 (ARM64), armv7, and ppc64le (IBM Power). Any Linux system that can build Rust and create a TUN interface can run WolfNet.

CLI Reference

wolfnet (Daemon & Management)

Command Description
wolfnet Run the WolfNet daemon (requires root)
wolfnet init --address <ip> Generate default config with the given WolfNet address
wolfnet invite Generate an invite token for another node (auto-detects public IP)
wolfnet join <token> Join a WolfNet network using an invite token
wolfnet genkey --output <path> Generate a new X25519 keypair
wolfnet pubkey Print this node’s base64 public key
wolfnet token Print public key and endpoint in token format

Options: --config <path> (default: /etc/wolfnet/config.toml), --debug (verbose logging)

wolfnetctl (Status & Monitoring)

Command Description
wolfnetctl status Show this node’s status (IP, port, gateway, uptime, peer count)
wolfnetctl list servers List all nodes on the network with their roles
wolfnetctl peers Detailed peer info: hostname, IP, endpoint, status, traffic, relay info
wolfnetctl info Combined status + peers output

wolfnetctl reads from /var/run/wolfnet/status.json — the daemon must be running.

Systemd Service

The installer creates a systemd service automatically. Common commands:

bash
# Start WolfNet
sudo systemctl start wolfnet

# Check status
sudo systemctl status wolfnet

# View logs
sudo journalctl -u wolfnet -f

# Enable on boot
sudo systemctl enable wolfnet

# Restart after config change
sudo systemctl restart wolfnet

# Reload config without restart (sends SIGHUP)
sudo kill -HUP $(pidof wolfnet)

Network Ports

Port Protocol Purpose
9600 UDP Encrypted tunnel traffic (handshakes, data, keepalives, PEX)
9601 UDP LAN auto-discovery broadcasts (local network only)

💡 Firewall tip: Only port 9600/UDP needs to be open for remote access. Port 9601 is used for LAN discovery only and does not need to be exposed to the internet.

File Locations

Path Purpose
/etc/wolfnet/config.toml Main configuration file
/etc/wolfnet/private.key X25519 private key (base64, mode 0600)
/var/run/wolfnet/status.json Live daemon status (updated every 5 seconds)
/var/run/wolfnet/routes.json Container/VM IP → host WolfNet IP routing table

Security

  • X25519 key exchange — Modern elliptic-curve Diffie-Hellman providing forward secrecy
  • ChaCha20-Poly1305 AEAD — Authenticated encryption used by WireGuard, TLS 1.3, and Google’s QUIC protocol
  • No central server — Peer-to-peer mesh with no single point of compromise
  • No third-party relay — All traffic stays on your own infrastructure
  • Token-based authentication — Only devices with a valid invite token can join
  • Replay protection — Monotonic nonce counters prevent packet replay attacks
  • Key isolation — Private key stored with mode 0600, never transmitted
  • Session re-establishment — Sessions are re-established on every handshake, resetting all counters

Troubleshooting

Peers not connecting

  • Check that UDP port 9600 is open in your firewall on both sides
  • Verify the endpoint address is correct: wolfnetctl peers shows the last known endpoint
  • For DynDNS, ensure the hostname resolves correctly: dig myhost.duckdns.org
  • Check logs for handshake errors: sudo journalctl -u wolfnet -f
  • Run with --debug for verbose output: sudo wolfnet --debug

Connected but can’t ping

  • Verify both nodes have wolfnet0 interface up: ip addr show wolfnet0
  • Check for conflicting firewall rules blocking the 10.10.10.0/24 subnet
  • Ensure the peer shows as connected: wolfnetctl peers

LAN discovery not working

  • Both nodes must be on the same Layer 2 network (same switch/VLAN)
  • Check that discovery = true in config on both nodes
  • Some cloud providers and corporate networks block UDP broadcast — use static endpoints instead

Connection drops after IP change

  • Use a DynDNS hostname instead of a static IP in the endpoint field
  • WolfNet re-resolves hostnames every 60 seconds automatically
  • Keepalives are sent every 25 seconds to maintain NAT mappings

WolfNet status shows no peers

bash
# Check if daemon is running
sudo systemctl status wolfnet

# Check status file
cat /var/run/wolfnet/status.json | python3 -m json.tool

# Check config
cat /etc/wolfnet/config.toml

# View public key (must match what peers have)
sudo wolfnet --config /etc/wolfnet/config.toml pubkey

Container not reachable via WolfNet

  • Verify the container has a WolfNet IP assigned in WolfStack
  • Check that routes are published: cat /var/run/wolfnet/routes.json
  • Ensure iptables FORWARD rules allow traffic between wolfnet0 and the container bridge
  • Restart WolfStack to reapply container routes: sudo systemctl restart wolfstack

High CPU when running alongside Tailscale

If you run WolfNet and Tailscale on the same machine and use Tailscale IPs (100.x.x.x) as WolfNet peer endpoints, a cross-tunnel routing loop can occur that causes both wolfnet and tailscaled to spike to high CPU usage (often 90%+ and 180%+ respectively).

What happens

Tailscale discovers that WolfNet peers are reachable at their WolfNet IPs (e.g. 10.10.10.236) via the wolfnet0 route. Tailscale then sends its own WireGuard tunnel traffic (UDP port 41641) to those IPs. The kernel routes this into wolfnet0, WolfNet encrypts it and sends it to the peer’s Tailscale endpoint — which goes back through Tailscale. The result is Tailscale traffic being double-encrypted and routed through itself in a feedback loop.

Symptoms

  • wolfnet at ~90% CPU, tailscaled at ~180% CPU
  • Stopping WolfNet immediately drops Tailscale CPU to near zero
  • Spikes are intermittent — they start when traffic flows between peers with Tailscale endpoints
  • tcpdump -i wolfnet0 -n -c 20 shows traffic on port 41641 (Tailscale’s WireGuard port)

How to diagnose

bash
# Check if Tailscale traffic is flowing through wolfnet0
sudo tcpdump -i wolfnet0 -n -c 20

# If you see packets on port 41641, that's the loop:
# 10.10.10.2.41641 > 10.10.10.236.41641: UDP, length 432

# Confirm with strace (run for ~10 seconds, then Ctrl+C)
sudo strace -c -p $(pidof wolfnet)
# Look for read/sendto counts in the hundreds of thousands

Fix: block Tailscale from using WolfNet IPs

From v0.5.17 onwards, WolfNet detects Tailscale automatically and applies this fix on startup. No manual action is needed — just update WolfNet. The details below are for reference or for older versions.

The fix is an iptables rule that prevents Tailscale’s WireGuard traffic (port 41641) from being routed into the WolfNet subnet. This does not affect WolfNet traffic — WolfNet uses its own port (default 9600) and continues working normally over Tailscale.

bash
# Block Tailscale WireGuard traffic from entering wolfnet subnet
# Replace 10.10.10.0/24 with your WolfNet subnet if different
sudo iptables -I OUTPUT -p udp --dport 41641 -d 10.10.10.0/24 -j DROP

# Make it persistent (Debian/Ubuntu)
sudo apt install -y iptables-persistent
sudo netfilter-persistent save

# Make it persistent (RHEL/Fedora)
sudo iptables-save > /etc/sysconfig/iptables

Apply this rule on every machine running both WolfNet and Tailscale. CPU usage should drop to near zero immediately.

Alternative: use public IPs instead of Tailscale IPs

If your peers have public IP addresses or DynDNS hostnames, use those as WolfNet endpoints instead of Tailscale 100.x.x.x addresses. This avoids double-encryption entirely — WolfNet connects directly and provides its own end-to-end encryption.

toml
# Instead of a Tailscale IP:
# endpoint = "100.112.240.87:9600"

# Use the real public IP or DynDNS hostname:
endpoint = "203.0.113.5:9600"
endpoint = "myhome.dyndns.org:9600"
Esc