Packet Format

Every openme knock is a single UDP datagram of exactly 165 bytes. Packets of any other size are silently discarded by the server.

Outer Packet

 0       1      33      45                   101                   165
 ┌───────┬──────┬───────┬─────────────────────┬─────────────────────┐
 │version│ephem │ nonce │     ciphertext      │    ed25519_sig      │
 │ 1 byte│pubkey│12bytes│      56 bytes        │      64 bytes       │
 │       │32 byt│       │  (plaintext + tag)  │                     │
 └───────┴──────┴───────┴─────────────────────┴─────────────────────┘
 ◄───────────────── signed portion (101 bytes) ────────────────────►
Field Offset Size Description
version 0 1 Protocol version. Currently 0x01.
ephemeral_pubkey 1 32 Client’s ephemeral Curve25519 public key for this knock.
nonce 33 12 ChaCha20-Poly1305 AEAD nonce (random, per knock).
ciphertext 45 56 Encrypted plaintext (40 bytes) + AEAD authentication tag (16 bytes).
ed25519_sig 101 64 Ed25519 signature over bytes 0–100 (the signed portion).

Total: 165 bytes.

The Ed25519 signature covers all fields except itself — bytes 0 through 100 inclusive. This prevents any field from being tampered with after signing.


Plaintext Payload

After successful AEAD decryption, the 40-byte plaintext contains:

 0           8          24         40
 ┌───────────┬──────────┬──────────┐
 │ timestamp │  random  │target_ip │
 │  8 bytes  │  nonce   │ 16 bytes │
 │ (int64 ns)│ 16 bytes │ (IPv6)   │
 └───────────┴──────────┴──────────┘
Field Offset Size Description
timestamp 0 8 Unix nanoseconds (big-endian int64). Used for replay window check.
random_nonce 8 16 128 bits of CSPRNG output. Used for nonce uniqueness check.
target_ip 24 16 IPv6 address (or IPv4-mapped IPv6) to open the firewall to. All-zeros = use source IP of knock packet.

No Port Field

The plaintext contains no port number. Ports are determined entirely by the server’s per-client configuration (allowed_ports in the server config). This is a deliberate security decision: the client cannot request ports it is not authorised for, and the attack surface of the packet format is minimised.

IPv4 and IPv6

target_ip is always 16 bytes. IPv4 addresses are stored as IPv4-mapped IPv6 addresses (::ffff:a.b.c.d). All-zeros (::) means “use the source IP of the knock packet.”


Size Breakdown

Component Size
version 1 byte
ephemeral_pubkey 32 bytes
nonce 12 bytes
timestamp (plaintext) 8 bytes
random_nonce (plaintext) 16 bytes
target_ip (plaintext) 16 bytes
AEAD tag 16 bytes
ed25519_sig 64 bytes
Total 165 bytes

Byte-Level Example

A valid knock from 192.168.1.10 requesting the firewall open for its own source IP:

Offset  Bytes (hex)                              Field
000000  01                                       version = 1
000001  a3f1...b2 (32 bytes)                     ephemeral_pubkey
000021  c4d5e6f7a8b9 (12 bytes)                  nonce
00002d  <56 bytes AEAD ciphertext+tag>            ciphertext
000065  <64 bytes Ed25519 signature>              ed25519_sig

Decrypted plaintext:

Offset  Bytes (hex)                              Field
000000  0000018c4a3b2100                         timestamp (ns since epoch)
000008  <16 random bytes>                        random_nonce
000018  00000000000000000000000000000000         target_ip = :: (wildcard → source IP)