#12492·fluent-bit

in_mqtt: QoS 2 flow never completes (PUBREL ignored), SUBSCRIBE never answered, second CONNECT accepted, Session Present always 1

Author: K-ANOYCreated Sep 30, 2026Updated Sep 30, 2026
Labelsstatus: waiting-for-triage

in_mqtt's server-side state machine handles only CONNECT, PUBLISH, PINGREQ and DISCONNECT; every other packet type is silently dropped, and CONNECT is handled the same way regardless of connection state. This breaks several mandatory server actions of MQTT 3.1.1.

Details

mqtt_prot_parser() dispatches:

c
if (conn->packet_type == MQTT_CONNECT) {
    mqtt_handle_connect(conn);
}
else if (conn->packet_type == MQTT_PUBLISH) { ... }
else if (conn->packet_type == MQTT_PINGREQ) { mqtt_handle_ping(conn); }
else if (conn->packet_type == MQTT_DISCONNECT) { ... return MQTT_HANGUP; }
else {
}
  1. PUBREL is never answered with PUBCOMP. For QoS 2, mqtt_handle_publish() sends PUBREC, so the client continues with PUBREL; that packet lands in the empty else and no PUBCOMP is sent. MQTT 3.1.1 §3.7: PUBCOMP "is the response to a PUBREL Packet"; §4.3.3 requires the receiver to "respond to a PUBREL packet by sending a PUBCOMP packet containing the same Packet Identifier as the PUBREL" [MQTT-4.3.3-2]. The client keeps the message in flight and, per §4.4, re-sends PUBREL on reconnect.
  2. SUBSCRIBE / UNSUBSCRIBE are never answered. §3.8.4: "When the Server receives a SUBSCRIBE Packet from a Client, the Server MUST respond with a SUBACK Packet" [MQTT-3.8.4-1] (the SUBACK can carry the failure code 0x80); §3.10.4 likewise for UNSUBACK. A client that subscribes waits indefinitely.
  3. A second CONNECT is accepted. The only state check is conn->status == MQTT_NEW && packet_type != MQTT_CONNECT (L393); in state MQTT_NEXT a CONNECT is answered with another CONNACK. §3.1.0: "The Server MUST process a second CONNECT Packet sent from a Client as a protocol violation and disconnect the Client" [MQTT-3.1.0-2].
  4. Session Present is always 1. mqtt_handle_connect() sets bit 0 of the acknowledge flags unconditionally (BIT_SET(buf[i], 0)) and never parses the CONNECT. §3.2.2.2: if the server accepts a connection with CleanSession = 1 it MUST set Session Present to 0 [MQTT-3.2.2-1]; with CleanSession = 0 it may only be 1 if the server has stored session state, which in_mqtt never keeps.
  5. Fixed-header flags / CONNECT contents are not validated (lower priority): flags of CONNECT/PINGREQ/DISCONNECT (must be 0000) are not checked [MQTT-2.2.2-2], and the CONNECT protocol name/level are never examined, so e.g. MQTT 5 clients are accepted with a 3.1.1 CONNACK instead of being refused with return code 0x01.

Trigger / observed result

Against an unmodified build (default in_mqtt settings). Every session starts with CONNECT 10 0d 00 04 4d 51 54 54 04 02 00 3c 00 01 63 (MQTT 3.1.1, CleanSession=1):

Client sends Server reply observed Expected
CONNECT (CleanSession=1) 20 02 01 00 (Session Present = 1) 20 02 00 00
PUBLISH QoS 2 id 7 34 0c 00 01 74 00 07 … 50 02 00 07 (PUBREC) PUBREC
then PUBREL 62 02 00 07 nothing within 1.5 s PUBCOMP 70 02 00 07
SUBSCRIBE 82 06 00 01 00 01 74 00 nothing within 1.0 s SUBACK (e.g. 90 03 00 01 80)
second CONNECT on the same connection 20 02 01 00 (another CONNACK) disconnect
PINGREQ with flags 1111 (cf 00) d0 00 (PINGRESP) disconnect

Reproduction files: issues/verification/fluentbit_inputs/ (send_cases.py, client_result.txt).

Impact

Interoperability / reliability. Standard clients publishing with QoS 2 never complete the exchange: the message is ingested once at PUBLISH time, but the client's in-flight window stays occupied and PUBREL is re-sent after reconnects; clients that (even incidentally) subscribe hang. Session Present = 1 tells a clean-session client that the server resumed a session it does not have. No memory-safety impact.

Suggested fix

In mqtt_prot_parser(): answer MQTT_PUBREL (flags must be 0010) with PUBCOMP carrying the same Packet Identifier; answer SUBSCRIBE with a SUBACK containing 0x80 for each filter (or close the connection if subscriptions are intentionally unsupported) and UNSUBSCRIBE with UNSUBACK; treat CONNECT in state MQTT_NEXT as a protocol error; in mqtt_handle_connect() send Session Present = 0 and validate protocol name "MQTT"/level 4 (return code 0x01 otherwise); reject invalid fixed-header flags.