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-serviceis the long-running local service. It owns the TUN interface, the control connection, P2P and relay paths, and the local command socket.sdlis the CLI front-end. It talks tosdl-serviceover 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.
- Download SDL. Open the download page and choose the release archive for your platform.
- Install the local service. Unpack the archive and run the installer for your OS. Linux and macOS use
sudo ./install.sh; Windows uses.\install.ps1from an elevated PowerShell. - Sign in to this website. Register or log in with Google or Microsoft, then open the dashboard.
- Generate an auth ticket. Click Generate auth ticket. Tickets are short-lived, so generate one when the device is ready.
- Authenticate the device. Copy the command shown on the dashboard, or run
sdl auth --userId <user-id> <ticket>locally. - Verify status. Run
sdl status. A healthy device should show the local service, authentication state, virtual IP, and gateway state. Usesdl status --jsonwhen 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/systemare not supported byinstall.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-tunby default and installs routes withip 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, installswintun.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/1and128.0.0.0/1) towardsdl-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.zaddresses, 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 through100.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-servicelogs 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.
