Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Handler and Validation

handler.rs implements the validation and dispatch half of the Server. It is reached from the receive loop after the datagram has been received, size-checked, rate limited, and decrypted. Its job is to deserialize the plaintext into ClientData, run the three validation checks, persist the counter, and hand a CommanderData to the commander over the Unix socket.

All functions here are impl Server methods with pub(super) visibility (callable from the parent server module but not outside the crate’s server tree).

Entry point: validate_and_send_command

#![allow(unused)]
fn main() {
pub(super) fn validate_and_send_command(
    &mut self,
    key_id: [u8; KEY_ID_SIZE],   // KEY_ID_SIZE == 8
    plaintext_data: [u8; PLAINTEXT_SIZE], // PLAINTEXT_SIZE == 57
    src_ip: IpAddr,
) -> anyhow::Result<()>
}

It first deserializes the plaintext:

#![allow(unused)]
fn main() {
ClientData::deserialize(plaintext_data)
}

ClientData::deserialize validates the protocol version byte (the first byte of the authenticated plaintext) and then reads fixed offsets out of the 58-byte buffer; an unknown version is rejected. The resulting struct is then matched against guard clauses, evaluated top to bottom. The first guard that matches produces an error and the packet is dropped; if none match, the success arm runs.

Step 1: replay check

#![allow(unused)]
fn main() {
client_data if self.blocklist.is_counter_replayed(key_id, client_data.counter) => ...
}

If is_counter_replayed returns true, the packet is rejected with:

Invalid counter for key {key_id:X?} - {counter} is on blocklist, expected > {server_counter:?}

is_counter_replayed uses a >= comparison against the stored per-key_id counter, so a counter equal to or below the last accepted value is a replay. The counter is a u128 nanosecond timestamp, so a freshly generated packet is normally strictly greater than the stored floor. See Blocklist and rate limiter.

Step 2: destination IP check

#![allow(unused)]
fn main() {
client_data if !self.config.ips.contains(&client_data.dst_ip) => ...
}

The dst_ip the client encoded into the packet must be one of the server’s configured ips. If it is not, the packet is rejected with:

Invalid host IP for key {key_id:X?} - expected {ips:?} to contain {destination_ip}

Both sides are compared as IpAddr. Config IPs are normalized at load time and dst_ip is normalized during deserialization, so an IPv4 address and its IPv6-mapped form compare equal. This check binds a captured packet to a specific destination host: replaying it against a different server IP fails.

Step 3: strict source IP check

#![allow(unused)]
fn main() {
client_data if client_data.is_source_ip_invalid(src_ip) => ...
}

is_source_ip_invalid is defined as:

#![allow(unused)]
fn main() {
pub(crate) fn is_source_ip_invalid(&self, source_ip: IpAddr) -> bool {
    self.strict && self.src_ip.is_some_and(|ip_sent| ip_sent != source_ip)
}
}

It only rejects when both conditions hold: the client set the strict flag, and it included a src_ip that does not match the real UDP source address (src_ip here is the normalize_ip’d sender from the receive loop). If strict is false, or no src_ip was sent, this check passes. Rejection message:

Invalid source IP for key {key_id:X?} - expected {client_src_ip_str}, actual {src_ip}

where client_src_ip_str is the sent src_ip or the literal "none".

Success arm

When no guard matched, the server logs an info line and dispatches:

#![allow(unused)]
fn main() {
info("Valid data for key {key_id:X?} - trying cmd {cmd} and counter {client_counter}|{server_counter:?} with {ip}");
self.update_block_list(key_id, client_data.counter);
self.send_command(CommanderData { cmd_hash: cmd, ip });
Ok(())
}

Note the order: the blocklist is updated before the command is sent. The IP forwarded to the commander is client_data.src_ip.unwrap_or(src_ip): the client-declared source IP if present, otherwise the real packet source.

update_block_list

#![allow(unused)]
fn main() {
pub(super) fn update_block_list(&mut self, key_id: [u8; KEY_ID_SIZE], counter: u128)
}

Calls blocklist.add(key_id, counter) then blocklist.save(). A save failure is logged via error(...) but does not abort the request: the in-memory counter is still advanced, so the replay check is correct for the rest of the process lifetime even if persistence failed.

send_command

#![allow(unused)]
fn main() {
pub(super) fn send_command(&self, data: CommanderData)
}

Wraps write_to_socket. On success it logs "Successfully sent data to commander". On failure it logs an error(...) including the socket path but swallows the error (returns nothing). A missing or unreachable commander socket therefore does not crash the server loop; it is logged and the next datagram is processed.

write_to_socket

#![allow(unused)]
fn main() {
pub(super) fn write_to_socket(&self, data: CommanderData) -> anyhow::Result<()>
}

Connects to the Unix socket at self.socket_path, converts the CommanderData into its 24-byte array ([u8; CMDR_DATA_SIZE] via From), writes all bytes with write_all, then flushes. Failures are wrapped with context: "Could not connect to socket {path}", "Could not write {bytes} to socket {path}", or "Could not flush stream for {path}".

What causes a packet to be dropped

Summarising the failure modes that prevent a command from running (each returns an Err that is logged and never sent to the client):

CauseWhereMessage fragment
Wrong datagram lengthreceive loopInvalid read count
Rate limit exceededcheck_rate_limitRate limit exceeded
Unknown key id / decrypt failuredecryptCould not find key for id
Replayed/old countervalidate_and_send_commandis on blocklist
Destination IP not configuredvalidate_and_send_commandInvalid host IP
Strict source IP mismatchvalidate_and_send_commandInvalid source IP
Commander socket unreachablesend_command (logged only)Could not send data to commander

The last row is special: validation already passed and the blocklist was updated, so the counter is consumed even though the command did not reach the commander.