# IdahoMesh Bridge Setup Build a one-way bridge between your tailnet and the IdahoMesh Meshtastic network. This lets your devices reach Nebra gateways through IdahoMesh, while preventing IdahoMesh from reaching back into your network. --- ## Architecture ``` Your Tailnet (your Headscale/Tailscale) ↓ (one-way only) [Bridge Machine] ← dual tailscaled, NAT + firewall ↓ IdahoMesh Tailnet (100.100.0.0/16) ↕ Nebra CM3 Gateways (Meshtastic nodes) ``` **Security model:** Your devices can reach Nebras. Nothing on IdahoMesh can initiate connections back into your network. NAT masquerades your source IPs so IdahoMesh only sees the bridge's IP. --- ## Prerequisites | Item | Value | |------|-------| | IdahoMesh Headscale URL | `https://vpn.idahomesh.com` | | IdahoMesh prefix | `100.100.0.0/16` | | Your preauthkey | Provided by IdahoMesh admin | You also need: 1. A Linux machine on your network (VM, Pi, bare metal — anything running systemd) 2. Root access on that machine 3. Internet access (to reach vpn.idahomesh.com) 4. Your own tailnet's connection details (Headscale URL, or stock Tailscale if using tailscale.com) --- ## Step 1: Install Tailscale ```bash curl -fsSL https://tailscale.com/install.sh | sh ``` This installs both `tailscale` and `tailscaled`. The default service (`tailscaled.service`) will handle your primary tailnet. --- ## Step 2: Set Up Dual tailscaled You need two Tailscale daemon instances — one for your tailnet, one for IdahoMesh. The default `tailscaled` service handles your tailnet. Create a second service for IdahoMesh. ### Create directories ```bash mkdir -p /var/lib/tailscale-meshtastic /var/run/tailscale-meshtastic ``` ### Create the second tailscaled service Write `/etc/systemd/system/tailscaled-meshtastic.service`: ```ini [Unit] Description=Tailscale daemon (IdahoMesh tailnet) After=network-online.target Wants=network-online.target [Service] ExecStart=/usr/sbin/tailscaled \ --state=/var/lib/tailscale-meshtastic/tailscaled.state \ --socket=/var/run/tailscale-meshtastic/tailscaled.sock \ --port=41642 \ --tun=tailscale1 Restart=always RestartSec=5 [Install] WantedBy=multi-user.target ``` > **Critical:** The `--tun=tailscale1` flag is required. Both instances cannot use the default `tailscale0` TUN device — the second one will fail with "TUN device tailscale0 is busy" if you omit this. Enable and start it: ```bash systemctl daemon-reload systemctl enable --now tailscaled-meshtastic ``` --- ## Step 3: Enable IP Forwarding ```bash cat > /etc/sysctl.d/99-bridge.conf << 'EOF' net.ipv4.ip_forward = 1 net.ipv6.conf.all.forwarding = 1 EOF sysctl -p /etc/sysctl.d/99-bridge.conf ``` --- ## Step 4: Join Both Tailnets ### Join your tailnet (default tailscaled) Advertise the IdahoMesh range so your other devices can route to Meshtastic nodes through this bridge: ```bash # If you use your own Headscale: tailscale up \ --login-server=https://YOUR_HEADSCALE_URL \ --advertise-routes=100.100.0.0/16 \ --accept-routes # If you use stock Tailscale (tailscale.com): tailscale up \ --advertise-routes=100.100.0.0/16 \ --accept-routes ``` After joining, you need to **approve the advertised route** on your tailnet's admin: - **Headscale:** `headscale routes list` then `headscale routes enable -r ` - **Stock Tailscale:** Go to admin console → Machines → your bridge → approve the `100.100.0.0/16` subnet route ### Join IdahoMesh (second tailscaled) ```bash tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock up \ --login-server=https://vpn.idahomesh.com \ --authkey=YOUR_IDAHOMESH_PREAUTHKEY \ --accept-routes ``` > **Important:** Do NOT advertise your tailnet's routes on IdahoMesh. The bridge is one-way — IdahoMesh should have no route back into your network. --- ## Step 5: Configure One-Way Firewall and NAT This is the critical security step. Your tailnet can reach IdahoMesh, but nothing on IdahoMesh can reach back into your network. Replace `YOUR_TAILNET_PREFIX` below with your tailnet's IP range. Common values: | Tailnet type | Prefix | |-------------|--------| | Stock Tailscale | `100.64.0.0/10` | | Custom Headscale | Check your `config.yaml` → `prefixes.v4` | ### Apply iptables rules ```bash # NAT: Masquerade your source IPs when going to IdahoMesh # Nebras see the bridge's IdahoMesh IP, not your real tailnet IPs iptables -t nat -A POSTROUTING -s YOUR_TAILNET_PREFIX -d 100.100.0.0/16 -j MASQUERADE # Allow your tailnet → IdahoMesh (outbound) iptables -A FORWARD -s YOUR_TAILNET_PREFIX -d 100.100.0.0/16 -j ACCEPT # Allow established/related return traffic only (responses to connections you initiated) iptables -A FORWARD -s 100.100.0.0/16 -d YOUR_TAILNET_PREFIX -m state --state ESTABLISHED,RELATED -j ACCEPT # DROP all new connections from IdahoMesh → your tailnet iptables -A FORWARD -s 100.100.0.0/16 -d YOUR_TAILNET_PREFIX -j DROP ``` ### Persist rules across reboots Do **not** use `iptables-persistent` — it hangs on install even with noninteractive mode. Use manual persistence instead: ```bash # Save current rules mkdir -p /etc/iptables iptables-save > /etc/iptables/rules.v4 ``` Create `/etc/systemd/system/iptables-restore.service`: ```ini [Unit] Description=Restore iptables rules Before=network-pre.target Wants=network-pre.target [Service] Type=oneshot ExecStart=/sbin/iptables-restore /etc/iptables/rules.v4 RemainAfterExit=yes [Install] WantedBy=multi-user.target ``` Enable it: ```bash systemctl daemon-reload systemctl enable iptables-restore ``` ### Verify rules ```bash iptables -L FORWARD -v -n iptables -t nat -L POSTROUTING -v -n ``` You should see: - MASQUERADE rule on POSTROUTING - ACCEPT for your prefix → 100.100.0.0/16 - ACCEPT ESTABLISHED,RELATED for 100.100.0.0/16 → your prefix - DROP for 100.100.0.0/16 → your prefix --- ## Step 6: Enable Routes on Your Other Devices Any device on your tailnet that wants to reach IdahoMesh Nebras through the bridge needs to accept subnet routes: ```bash # On each device that needs access tailscale set --accept-routes ``` Without this, traffic to `100.100.0.x` goes to the default gateway instead of through the Tailscale tunnel. --- ## Step 7: Verify ### Check both tailscaled instances ```bash # Your tailnet tailscale status # IdahoMesh tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock status ``` Both should show "online" with peers listed. ### Ping a Nebra gateway ```bash # From the bridge itself, via IdahoMesh socket tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock ping burley-butte ``` ### Ping from another device on your tailnet ```bash # From any device on your tailnet (with --accept-routes enabled) # Use the Nebra's IdahoMesh IP ping 100.100.0.3 # Burley Butte ``` ### Verify isolation From an IdahoMesh device or ask the admin to test — pinging your tailnet IPs from IdahoMesh should time out / be unreachable. --- ## Troubleshooting ### Second tailscaled won't start - Check `journalctl -u tailscaled-meshtastic -f` - Most common: forgot `--tun=tailscale1` — both instances fighting over `tailscale0` - Verify the port isn't in conflict: default uses 41641, second uses 41642 ### Can't reach Nebras from other devices on your tailnet - Verify the bridge advertises `100.100.0.0/16`: `tailscale status` should show it as a subnet router - Approve the route on your tailnet admin (Headscale or Tailscale admin console) - Enable `--accept-routes` on the client device trying to reach Nebras ### IdahoMesh preauthkey expired - Contact the IdahoMesh admin for a new key - Re-join: `tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock up --login-server=https://vpn.idahomesh.com --authkey=NEW_KEY --accept-routes` ### After reboot, only one tailscaled reconnects - Check both services: `systemctl status tailscaled` and `systemctl status tailscaled-meshtastic` - Verify iptables rules survived: `iptables -L FORWARD -v -n` - If the second instance lost state, re-join IdahoMesh with a new preauthkey ### Default tailscaled pointed at wrong server after force-reauth - If you run `tailscale up --force-reauth` without specifying `--login-server`, it may reconnect to the wrong Headscale - Always specify `--login-server` explicitly when re-authing: - Default instance: `tailscale up --login-server=https://YOUR_HEADSCALE_URL --force-reauth` - IdahoMesh instance: `tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock up --login-server=https://vpn.idahomesh.com --force-reauth` --- ## Quick Reference | Item | Value | |------|-------| | IdahoMesh URL | `https://vpn.idahomesh.com` | | IdahoMesh prefix | `100.100.0.0/16` | | IdahoMesh socket | `/var/run/tailscale-meshtastic/tailscaled.sock` | | IdahoMesh TUN device | `tailscale1` | | IdahoMesh port | `41642` | | Default Tailscale socket | `/var/run/tailscale/tailscaled.sock` | | Default TUN device | `tailscale0` | | Default port | `41641` | ### Useful commands ```bash # IdahoMesh status tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock status # IdahoMesh ping tailscale --socket=/var/run/tailscale-meshtastic/tailscaled.sock ping # Check firewall rules iptables -L FORWARD -v -n iptables -t nat -L POSTROUTING -v -n # Restart services systemctl restart tailscaled # Your tailnet systemctl restart tailscaled-meshtastic # IdahoMesh ``` --- *Last updated: 2026-02-11*