Knocking
Single Packet Authorization (SPA) is a lightweight authentication mechanism that lets a client prove its identity to a server with a single UDP packet, before any connection is attempted. Unlike a conventional login where authentication happens after the connection is established, SPA reverses this order: the server remains completely silent — it does not respond and gives no indication that any service is running — unless and until it receives a valid knock.
In Desomnia, SPA serves a specific purpose: protecting the wake-up mechanism from being triggered by unsolicited traffic arriving from outside the local network. A port scanner or automated bot probing your router’s public IP will receive no response and cannot wake your hosts. A legitimate client that sends a valid knock has its IP temporarily authorized, and any connection attempt from that IP to a watched host during the authorization window can trigger a wake-up.
Note
SPA authenticates access — it does not encrypt your data traffic. If the connection itself needs to be private, combine SPA with application-level encryption such as SSH or TLS.
How it works
The exchange involves two sides: the sender (the client connecting from outside) and the receiver (the always-on device running Desomnia in proxy mode inside your network).
Sender side
When the sender wants to connect to a service on a sleeping host, it first transmits a single UDP packet — the knock — to a configured port on the receiver. The knock packet contains credentials (a shared secret or encrypted payload, depending on the method) that the receiver can validate. The sender then waits briefly and proceeds with the connection attempt, trusting that the receiver has processed the knock.
Desomnia’s built-in knock sender can automate this entirely: when onServiceDemand="knock" is configured on a remote host, Desomnia detects the outgoing connection attempt, sends the knock automatically, and forwards the connection once the target host is awake. No manual step is required.
Receiver side
The receiver does not open a listening socket on the knock port. Instead, it captures knock packets passively, the same way it captures other network traffic. This is significant: a port scan will find the knock port closed, because there is nothing listening on it in the conventional sense.
When a knock packet arrives, the receiver validates it. If validation passes, the sender’s IP address is added to a temporary trusted range for the duration of knockTimeout. Any connection attempt from that IP to a watched host during this window can trigger a wake-up. When the window expires, the IP is removed and access is revoked automatically.
If validation fails — wrong secret, expired timestamp, malformed packet — the receiver discards the packet silently. Nothing is sent back.
On the receiver, the SPA listener is configured through a <DynamicHostRange> element placed inside a <ForeignHostFilterRule>. This single element holds the knock method, port, timeout, validation options, and shared secrets. When a valid knock arrives, the sender’s IP is dynamically added to that range for the duration of the authorization window.
Knock methods
Two knock methods are available, with different security properties.
Plain Text
The plain method transmits the shared secret as a UTF-8 string inside the UDP payload. There is no encryption and no replay protection. Anyone who captures the packet can read the secret and resend it.
This method protects against port scanners and automated bots, which do not know the knock sequence and have no reason to look for it. It does not protect against a targeted attacker who can observe traffic on the network path between sender and receiver.
Firewall Knock Operator
The fko method is based on the open fwknop protocol (Firewall Knock Operator). The payload is encrypted with AES and can be authenticated with an HMAC, making it resistant to both eavesdropping and message forgery. Optionally, a timestamp can be embedded in the payload: when proofTime is configured on the receiver, a captured packet becomes invalid once its timestamp falls outside the configured acceptance window, preventing replay attacks.
The fko method is interoperable with the original fwknop client, so Desomnia can receive knocks from any fwknop-compatible sender, and vice versa.
See Firewall Knock Operator for configuration details and the full list of supported fwknop features.
Comparison
Feature |
|
|
|---|---|---|
Payload encryption |
❌ No |
✅ AES (128 / 192 / 256-bit) |
Replay protection ( |
❌ No |
✅ Optional (timestamp) |
Message authentication (HMAC) |
❌ No |
✅ Optional but recommended |
fwknop interoperability |
❌ No |
✅ Yes |
Configuration complexity |
Low |
Medium |
Suitable for internet-facing exposure |
⚠️ Limited |
✅ Yes |
Use plain for testing, or in scenarios where the knock traffic cannot be observed by an adversary (for example, on a trusted internal network). For any deployment exposed to the internet for an extended period, use fko.
Configuration options
Knock behaviour is configured on both sides. On the receiver, all SPA settings belong to the <DynamicHostRange> element:
<ForeignHostFilterRule>
<DynamicHostRange name="trusted"
knockMethod="plain"
knockPort="62201"
knockTimeout="30s">
<SharedSecret encoding="UTF-8">my-passphrase</SharedSecret>
</DynamicHostRange>
</ForeignHostFilterRule>
On the sender, knock attributes are set on <RemoteHost>, with <NetworkMonitor> available as an inherited default and individual <Service> elements available for per-service overrides.
The key attributes are:
knockMethodThe knock method to use:
plainorfko. Must match on sender and receiver.knockPortThe UDP port the receiver listens on and the sender targets. Must match on both sides. Forward this port on your router to make it reachable from outside the network.
knockTimeoutOn the receiver: how long the sender’s IP remains authorized after a successful knock. Should be long enough for the target host to wake up and accept connections — typically 20–60 seconds depending on your hardware.
On the sender: the total time Desomnia will keep trying to send the knock. Should be at least as long as the receiver’s
knockTimeout.knockDelayOn the sender: how long to wait after detecting a connection attempt before sending the knock.
knockRepeatOn the sender: if set, Desomnia resends the knock at this interval until
knockTimeoutexpires, as a guard against occasional packet loss.
Knock attributes can be set at the <NetworkMonitor> level as inherited defaults, overridden at the <RemoteHost> level, or further overridden at the <Service> level.
Receiver-only
Some options are only valid in the context of configuring the SPA receiver:
proofIPOn the receiver: when set to
true, Desomnia checks that the source IP address observed in the knock packet matches the IP embedded in the SPA payload. This ensures that nobody has tampered with the packet’s source address in transit. This check works correctly when the sender is directly reachable from the internet. For senders behind NAT, the embedded IP is the sender’s private address, which will not match the public IP observed by Desomnia — support for NAT traversal via external IP lookup is planned for a future release.proofTimeOn the receiver: a duration that specifies the acceptable deviation between the timestamp embedded in the SPA payload and the receiver’s current time. When set, any knock whose timestamp falls outside this window is rejected, preventing replay attacks. The value should be generous enough to account for clock differences between sender and receiver — a few minutes is typically sufficient. Supported by
fkoonly;plaincarries no timestamp.
For a complete worked example, see Remote Access.
Generating keys
Cryptographically strong keys are required for the fko method. The following tools produce suitable random keys in Base64-encoded format.
Using OpenSSL
- OS:
🐧 Linux 🍎 macOS
openssl rand -base64 32
This generates a 256-bit (32-byte) key and writes it in Base64-encoded format to the terminal:
1RNh13FmfBTiMT+/VPEMVXUnRXtg+2/nbVVY+O4ENcs=
Via PowerShell
- OS:
🪟 Windows
$bytes = New-Object byte[] 32
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
[Convert]::ToBase64String($bytes)
This also produces a suitable Base64-encoded string in your terminal.
Run the command twice to generate a separate encryption key and HMAC authentication key, as recommended by the fko method.