SDL logoSDL

Documentation

Install SDL, register your device, and verify connectivity across Linux, macOS, and Windows.

Overview

SDL (Software Defined LAN) connects machines across WAN, the Internet, and NAT into one overlay LAN. A central control plane authenticates devices and hands out virtual IP addresses; data traffic is forwarded by separate gateway instances, so the control plane and data plane scale independently.

Each machine runs two binaries from the release package:

  • sdl-service is the long-running local service. It owns the TUN interface, the control connection, P2P and relay paths, and the local command socket.
  • sdl is the CLI front-end. It talks to sdl-service over a local socket for status, auth, gateway selection, rename, suspend, and resume.

Because sdl-service creates a virtual network interface, installation and service management require administrator or root privileges. Day-to-day CLI use (sdl status, sdl list, etc.) does not.

Getting Started

This is the shortest path for a new user joining an SDL network.

  1. Download SDL. Open the download page and choose the release archive for your platform.
  2. Install the local service. Unpack the archive and run the installer for your OS. Linux and macOS use sudo ./install.sh; Windows uses .\install.ps1 from an elevated PowerShell.
  3. Sign in to this website. Register or log in with Google or Microsoft, then open the dashboard.
  4. Generate an auth ticket. Click Generate auth ticket. Tickets are short-lived, so generate one when the device is ready.
  5. Authenticate the device. Copy the command shown on the dashboard, or run sdl auth --userId <user-id> <ticket> locally.
  6. Verify status. Run sdl status. A healthy device should show the local service, authentication state, virtual IP, and gateway state. Use sdl status --json when you need machine-readable output.

If sdl status still shows auth_pending, issue a fresh ticket and run sdl auth again. If the service is not running, restart sdl-service with your platform service manager.

System Requirements

SDL release packages are built for the platforms listed below. Other platforms may be possible from source, but they are not part of the current published release set.

Platform Official release target Supported OS versions Required OS features Privileges Runtime dependencies
Linux x86_64 x86_64-unknown-linux-musl systemd-based x86_64 distributions, including Ubuntu 20.04/22.04/24.04, Debian 11/12, Rocky Linux / AlmaLinux / RHEL 8 or 9, Fedora 38+, openSUSE Leap 15.x, and Arch Linux Linux kernel TUN support (/dev/net/tun), systemd with /run/systemd/system, systemctl, /bin/sh, ip from iproute2. The legacy route command is only used as a fallback. root via sudo for install, service registration, TUN creation, and route changes No glibc requirement for the official package because it is a musl static build.
macOS Apple Silicon aarch64-apple-darwin macOS 12 Monterey or newer on Apple Silicon is the intended target; macOS 13 Ventura, 14 Sonoma, and 15 Sequoia are expected to work launchd / launchctl, built-in utun support, /bin/sh, and the system route command root via sudo for install, LaunchDaemon registration, utun setup, and route changes No third-party runtime dependency. Intel macOS is not currently published as an official release artifact.
Windows x86_64 x86_64-pc-windows-msvc Windows 10 1809 or newer, Windows 11 21H2 or newer, Windows Server 2016, 2019, 2022, and 2025 Windows Service Control Manager, PowerShell 5.1 or newer for install.ps1, cmd.exe, and IPv4 route management Administrator for install, service registration, Wintun driver use, and route changes. Ordinary users can run day-to-day sdl CLI commands after install. wintun.dll is shipped in the release zip and installed next to the executables.

Linux notes:

  • The installer intentionally rejects non-systemd hosts. Containers, WSL instances, OpenRC-based distributions, and minimal images without /run/systemd/system are not supported by install.sh.
  • The official Linux artifact is x86_64 only. ARM64 Linux can be treated as a source-build target later, but it is not currently published by the release workflow.
  • The service creates a TUN device named sdl-tun by default and installs routes with ip route replace. Existing local routes can still conflict with SDL virtual routes.

Windows notes:

  • The installer requires an elevated PowerShell because it registers a native Windows service, writes under C:\Program Files\SDL, installs wintun.dll, and configures machine PATH.
  • 32-bit Windows and Windows on ARM are not supported by the current release package.
  • Windows 7/8/8.1 are not supported by the current installer or release policy.

Network and disk:

  • Outbound HTTPS to the control plane, default https://control.middlescale.net/control.
  • UDP, QUIC, or HTTPS access to SDL gateway relays, depending on the gateway policy selected by the control plane and local network conditions.
  • A few MB of disk space for binaries, identity files, profiles, and rolling logs.

Download

Grab the release archive for your platform from the download page. Each archive contains:

  • Linux / macOS: sdl, sdl-service, install.sh, README.txt
  • Windows: sdl.exe, sdl-service.exe, wintun.dll, install.ps1, README.txt

Install on Linux

From the unpacked release folder, run:

sudo ./install.sh

The installer copies sdl and sdl-service under /opt/sdl, symlinks them into /usr/local/bin, generates a persistent device identity under /opt/sdl/env, and registers a systemd unit named sdl-service that starts immediately.

Installed layout:

/opt/sdl/
  sdl
  sdl-service
  env/                         # machine identity and persistent service state
    config.json                # active profile, or active_user_id pointer in multi-user mode
    device-id                  # machine identity id
    device.key                 # machine private identity key
    service-state.json         # service/auth/runtime state for the CLI
    command.sock               # local CLI-to-service socket, created at runtime
    service.lock               # local service lock, created at runtime
  profiles/                    # saved profiles used by sdl switch
    hxm.json                   # saved profile for user hxm
    sdl-cdba6d9b6ebb53ca.json  # saved profile for another user
    ent-acme-admin.json        # saved profile for a future enterprise user
  log/
    sdl-service.log            # macOS rolling log; Linux uses journald by default

The profiles directory stores one saved profile per SDL user. env/config.json stores the active profile for older single-user installs, or an active_user_id pointer in multi-user mode. sdl switch --userId <user-id> changes that active user and makes sdl-service load the matching file from profiles/. Keep both env and profiles when upgrading if you want to preserve the device identity and saved profiles.

Manage the service with systemd:

systemctl status sdl-service
systemctl restart sdl-service
journalctl -u sdl-service -f

Install on macOS

macOS uses the same install.sh; the installer detects the platform and writes a launchd plist instead of a systemd unit:

sudo ./install.sh

The service label is net.middlescale.sdl-service, kept alive automatically (KeepAlive). Manage it with launchctl:

sudo launchctl kickstart -k system/net.middlescale.sdl-service
sudo launchctl bootout system /Library/LaunchDaemons/net.middlescale.sdl-service.plist

Binaries live under /opt/sdl and are symlinked into /usr/local/bin, identical to Linux.

Install on Windows

Unzip the release archive, right-click PowerShell and Run as Administrator, then from the unzipped folder:

.\install.ps1

The installer copies sdl.exe, sdl-service.exe, and wintun.dll to C:\Program Files\SDL, creates env\ and log\, generates a persistent device identity, registers a native Windows service named sdl-service (delayed automatic start, restart on failure), and adds the install directory to the machine PATH.

Installed layout:

C:\Program Files\SDL\
  sdl.exe
  sdl-service.exe
  wintun.dll
  env\                         # machine identity and persistent service state
    config.json                # active profile, or active_user_id pointer in multi-user mode
    device-id                  # machine identity id
    device.key                 # machine private identity key
    service-state.json         # service/auth/runtime state for the CLI
  profiles\                    # saved profiles used by sdl switch
    hxm.json                   # saved profile for user hxm
    sdl-cdba6d9b6ebb53ca.json  # saved profile for another user
    ent-acme-admin.json        # saved profile for a future enterprise user
  log\
    sdl-service.log            # rolling service log

The profiles directory stores one saved profile per SDL user. env\config.json stores the active profile for older single-user installs, or an active_user_id pointer in multi-user mode. sdl switch --userId <user-id> changes that active user and makes sdl-service load the matching file from profiles\. Keep both env and profiles when upgrading if you want to preserve the device identity and saved profiles. If the target env\device-id or env\device.key is missing, install.ps1 can import those files from the unpacked release directory's env\.

The service name sdl-service is compiled into sdl-service.exe and cannot be changed by the installer.

The env\ directory is shared: ordinary (non-admin) users get read/traverse access so they can run sdl status, sdl auth, and friends, while the private device.key stays restricted to SYSTEM and Administrators. Writes to shared config/state go through the elevated service over the local command socket.

Manage the service:

Get-Service sdl-service
Restart-Service sdl-service
Stop-Service sdl-service
Start-Service sdl-service

Exit Node

An SDL exit node lets one device send general Internet-bound IPv4 traffic through another SDL device. The common case is a laptop or desktop using a remote Linux server as its network egress while still staying connected to the SDL mesh.

Current support:

  • Client side: Linux, macOS, and Windows can use an approved SDL exit node.
  • Exit-node side: Linux only.
  • Traffic scope: IPv4 full-tunnel routing. IPv6 exit-node routing is not part of the current release.

How it works

SDL exit-node mode has two sides:

  • Node A, the client: installs split default routes (0.0.0.0/1 and 128.0.0.0/1) toward sdl-tun. This sends ordinary IPv4 Internet traffic into SDL.
  • Node B, the exit node: receives packets from SDL, enables Linux forwarding, and applies NAT/MASQUERADE on the selected egress interface.

The client does not simply route everything blindly into the tunnel. SDL automatically excludes the addresses that are needed to keep the overlay itself alive:

  • the control-plane endpoint;
  • active gateway relay endpoints;
  • direct P2P underlay endpoints for SDL peers;
  • any extra addresses supplied with --exclude.

These exclusions prevent transport loops, where the packets needed to reach the exit node would themselves be routed into the exit-node tunnel. SDL mesh traffic, such as traffic to another device's virtual IP, remains reachable while the exit node is active.

DNS is part of the exit-node behavior. When a client selects an exit node, SDL configures SDL DNS as a global resolver for the client. This allows normal domain lookups to use the SDL path instead of leaking through the local network resolver. When the selection is cleared, SDL restores the saved DNS state.

Enable an exit node on Linux

On the Linux device that should provide Internet egress, run:

sdl exit-node enable --egress-interface eth0

Replace eth0 with the interface that has Internet access.

This command prepares the local Linux routing side:

  • enables IPv4 forwarding if it is not already enabled;
  • adds NAT/MASQUERADE rules for traffic leaving the egress interface;
  • records that this device wants to advertise exit-node capability to the control plane.

SDL does not automatically turn IPv4 forwarding back off later. Leaving net.ipv4.ip_forward=1 is normally safe on its own; forwarding only becomes meaningful when firewall/NAT rules allow traffic through. SDL leaves it enabled because Docker, routers, VPN software, or other services may also depend on it.

The control plane must approve the device before other clients can use it:

sdl-admin exit-node list --id <user-id>
sdl-admin exit-node approve --device-id <device-id>

Check the local state:

sdl exit-node status

Stop advertising the local machine as an exit node:

sdl exit-node disable

Disabling removes the SDL NAT/FORWARD rules for this exit-node configuration. Admin approval is not removed automatically; an administrator can revoke or leave it in place depending on policy.

Use an exit node from a client

List usable exit nodes:

sdl exit-node list

Select one by name, virtual IP, or device ID:

sdl exit-node use <exit-node-name|virtual-ip|device-id>

For example, if sdl exit-node list shows an exit node named office-hk, you can run sdl exit-node use office-hk.

Check the current selection:

sdl exit-node status

Clear the selection and restore local routing/DNS:

sdl exit-node clear

The selected exit node name/device is saved as the last selection in the active SDL profile, but the active full-tunnel state is not restored automatically after sdl-service restarts. This is intentional: sdl exit-node use changes the system default route and DNS, so SDL requires an explicit user command after a service or system restart before it enables the exit node again.

When sdl-service stops or restarts, SDL resets the client-side exit-node routes and DNS state. sdl exit-node status can still show the last selected exit node as a convenience, but Using exit node should be treated as disabled until you run sdl exit-node use <target> again.

If another VPN has already installed full-tunnel split routes, sdl exit-node use may refuse to proceed and print a warning. Disable the other full-tunnel route first, then retry.

Tailscale compatibility

SDL can coexist with Tailscale, but only one product should own full-tunnel DNS at a time.

Routing compatibility:

  • If Tailscale is running without a Tailscale exit node, SDL can install its own split default routes for sdl exit-node use.
  • Tailscale peer routes, such as 100.x.y.z addresses, can continue to work through the Tailscale interface.
  • SDL excludes required control, gateway, and peer underlay endpoints from the SDL full tunnel so the SDL transport path does not route back into itself.
  • Do not enable a Tailscale exit node and an SDL exit node on the same client at the same time. Both features want to own the default route.

DNS compatibility:

  • SDL exit-node mode configures SDL DNS as a global resolver (~.) for the client.
  • When Tailscale accept-dns=true, Tailscale is allowed to rewrite the system DNS configuration and install its own global DNS route (~.), usually through 100.100.100.100.
  • SDL exit-node mode also needs to own global DNS so regular domain lookups follow the SDL exit-node path. If both SDL and Tailscale install global DNS routes, the system resolver may choose either one based on interface priority, cache state, or platform-specific resolver behavior.
  • This can cause two concrete problems: DNS queries may bypass the SDL exit node and go through Tailscale/local DNS instead, and DNS behavior can change after a restart or network change even when SDL routing still looks correct.
  • Recommended setting when using SDL exit node and Tailscale together:
sudo tailscale set --accept-dns=false

This does not stop Tailscale itself. It only tells Tailscale not to manage the system resolver, leaving SDL free to install the exit-node DNS route while Tailscale peer connectivity continues to use the Tailscale interface.

Check the current Tailscale DNS state:

tailscale dns status

With accept-dns=false, Tailscale data-plane connectivity can remain active, but Tailscale MagicDNS names may no longer resolve through the system resolver. Use Tailscale IP addresses directly, or temporarily re-enable Tailscale DNS when MagicDNS is more important than SDL exit-node DNS:

sudo tailscale set --accept-dns=true

Common Commands

sdl status              # local service, auth, network, route, gateway state
sdl status --json       # machine-readable, includes auth_pending / last_error
sdl list                # peers and their reachability
sdl gateway --json      # gateway candidates and active selection
sdl gateway --set auto  # return gateway selection to automatic
sdl gateway --set <name> # pin a specific gateway
sdl route --json        # current forwarding path
sdl channel_change --type relay   # force relay channel
sdl channel_change --json
sdl rename <new-name>   # update display name (restart sdl-service to apply locally)
sdl suspend             # pause traffic handling without exiting the service
sdl resume              # resume an existing runtime or recreate from saved config

Logs

Log destination is platform-specific:

  • Linux: sdl-service logs to stderr, captured by journald: journalctl -u sdl-service -f
  • macOS: rolling file at /opt/sdl/log/sdl-service.log (10 MB x 5 archives).
  • Windows: rolling file at C:\Program Files\SDL\log\sdl-service.log (10 MB x 5 archives).

The default level is info. Override it with the RUST_LOG environment variable (for example, debug): on Linux/macOS set it in the service environment; on Windows set the service environment via the registry key HKLM\SYSTEM\CurrentControlSet\Services\sdl-service\Environment (a MultiString value). An optional log4rs.yaml placed next to the binary overrides the built-in logging on every platform.

Uninstall

Linux

sudo systemctl disable --now sdl-service
sudo rm /etc/systemd/system/sdl-service.service
sudo systemctl daemon-reload
sudo rm -rf /opt/sdl /usr/local/bin/sdl /usr/local/bin/sdl-service

macOS

sudo launchctl bootout system /Library/LaunchDaemons/net.middlescale.sdl-service.plist
sudo rm /Library/LaunchDaemons/net.middlescale.sdl-service.plist
sudo rm -rf /opt/sdl /usr/local/bin/sdl /usr/local/bin/sdl-service

Windows

In an elevated PowerShell, from the unzipped release folder:

.\install.ps1 -Uninstall

This stops and deletes the service, removes the install directory, and drops the install dir from the machine PATH.