Cordon is a single packet authorization gate. It keeps guarded ports closed and opens one only when it receives a single authenticated UDP packet — a knock — from a client that holds the shared secret. The gate never replies, so to a scanner the port is simply not there. A correct knock opens the requested port for the source address for a short lease, and the lease expires on its own.
One binary, standard library only. It runs unprivileged with an in-process firewall by default, and drives nft,
pf or iptables when you ask it to.
$ cordon serve -secret "$CORDON_SECRET" -ports 22,443 -ttl 30s
$ cordon knock -to gate.example.com:62201 -secret "$CORDON_SECRET" -port 22
knock sent to gate.example.com:62201: port 22, nonce 882533ebThe knock is 59 bytes: a version, a random nonce, a timestamp, the requested port, and an HMAC-SHA256 tag over the rest. The gate verifies the tag in constant time, rejects a stale timestamp, rejects a nonce it has already seen, and — only then — opens the port.
The console shows the gate as a checkpoint. The barrier is down and the lamp red while the gate is secured; a valid knock lifts the barrier, turns the lamp green, and drops a lease onto the requested port with a live countdown. Denied knocks land in the ledger with their reason — a bad tag, a stale packet, a replay, or a port that is not on the allow-list.
The demo runs a gate with the in-process firewall and a client that knocks on a loop — valid knocks on the allowed ports, and a couple of bad ones so you can watch them be turned away.
make demo # panel at http://127.0.0.1:18210
make demo-downgo install github.com/cansarihan/cordon/cmd/cordon@latestor build from source:
git clone https://github.com/cansarihan/cordon
cd cordon
make build # ./bin/cordonBy default the gate tracks leases in process and touches nothing on the host. To open an actual port, give it a
shell template for open and close; {ip} and {port} are substituted before the command runs.
cordon serve -secret "$CORDON_SECRET" -ports 22 \
-open "nft add element inet filter cordon { {ip} }" \
-close "nft delete element inet filter cordon { {ip} }"Set up the nft set once and add the accept rule that reads it; the templates only add and remove members.
configs/firewall-templates.md has the nft, pf and iptables recipes, and
configs/cordon.service is a systemd unit that runs the gate with CAP_NET_ADMIN and
nothing else.
cordon serve -listen 127.0.0.1:8080 -knock 0.0.0.0:62201 -secret ... -ports 22,443 -ttl 30s
cordon knock -to host:62201 -secret ... -port 22
cordon version
serve flags: -window (accepted clock skew, default 30s), -reap (lease sweep interval, default 5s),
-open/-close (shell templates; presence switches on the shell backend). The secret can also come from
CORDON_SECRET.
- Default closed. Nothing is open until a knock opens it, and every lease expires on its own.
- Silent. The gate never answers a packet, so it gives a scanner no signal.
- Authenticated and fresh. HMAC-SHA256 over the packet, a constant-time compare, a timestamp window, and a nonce cache against replay.
- Unprivileged by default. The in-process firewall needs no root; the shell backend is opt-in.
docs/architecture.md explains how the pieces fit together.
MIT — see LICENSE.


