Skip to content

broker: read-timeout uses client-requested keep-alive, not negotiated ServerKeepAlive #80

@fabracht

Description

@fabracht

Summary

When the broker is configured with BrokerConfig.server_keep_alive and overrides the client's requested keep-alive via the CONNACK ServerKeepAlive property, the broker continues to use the client's original requested value for its own read-timeout instead of the value it just imposed.

This was flagged by @butterflyfish in #78 and confirmed during follow-up review. The client side was fixed in #78; this issue tracks the symmetric broker-side fix.

Where

  • crates/mqtt5/src/broker/client_handler/connect.rs:95self.keep_alive = Duration::from_secs(u64::from(connect.keep_alive)); sets the broker's internal value from the CONNECT packet (client's request).
  • crates/mqtt5/src/broker/client_handler/connect.rs:403-407 — the broker writes ServerKeepAlive to CONNACK from config.server_keep_alive, but never updates self.keep_alive to the overridden value.
  • crates/mqtt5/src/broker/client_handler/mod.rs:616read_timeout = self.keep_alive.mul_f32(1.5) consequently uses the client's original value.

Effect

If the broker is configured with server_keep_alive = Some(10s) and the client requests 60s:

Functionally safe in the spec-compliant case — the broker is just more permissive than its own policy implies. But:

  1. With a non-compliant or older client that ignores ServerKeepAlive, the broker would fail to detect a hung connection within the intended window.
  2. It defeats the operational intent of server_keep_alive (typically set to detect dead connections sooner than the client's preferred cadence).

Suggested fix

In connect.rs after writing the ServerKeepAlive CONNACK property (line 403-407), also update self.keep_alive:

if let Some(keep_alive) = self.config.server_keep_alive {
    let secs = u16::try_from(keep_alive.as_secs()).unwrap_or(u16::MAX);
    connack.properties.set_server_keep_alive(secs);
    self.keep_alive = Duration::from_secs(u64::from(secs));
}

(The u16 clamp logic should probably share a helper with the client-side configured_keep_alive_u16 introduced in #78.)

Test

Add a broker-side integration test that:

  1. Configures broker with server_keep_alive = Some(small_value).
  2. Connects a client that requests a much larger keep-alive but doesn't send PINGREQ (simulate a non-compliant client by disabling its keepalive task).
  3. Asserts the broker disconnects the client within ~small_value * 1.5, not within ~large_value * 1.5.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions