Skip to content

MQTTv5 over QUIC sometimes blocks on nng_close #257

Description

@nbusser-sr

Describe the bug

I took inspiration from demo/quic_mqttv5 for a very simple MQTTv5 over QUIC client, which connects to the broker, and subscribe on connection.

#include "nng/mqtt/mqtt_client.h"
#include "nng/mqtt/mqtt_quic.h"
#include <nng/nng.h>
#include <nng/protocol/pubsub0/pub.h>
#include <nng/protocol/pubsub0/sub.h>
#include <nng/supplemental/util/platform.h>
#include <nng/transport/ipc/ipc.h>
#include <stdio.h>
#include <unistd.h>

// NOLINTBEGIN

nng_msg *
AllocateMessage()
{
	nng_msg *message = NULL;
	nng_mqtt_msg_alloc(&message, 0);
	return message;
}

int
SendMessage(nng_msg *message, nng_socket *socket)
{
	const int outcome = nng_sendmsg(*socket, message, NNG_FLAG_ALLOC);
	if (outcome == 0) {
		return 0;
	}
	nng_msg_free(message);

	fprintf(stderr, "Failed to send MQTT message: %s\n",
	    nng_strerror(outcome));
	return 1;
}

nng_msg *
BuildConnectionMessage()
{
	nng_msg *const connection_message = AllocateMessage();

	nng_mqtt_msg_set_connect_proto_version(
	    connection_message, MQTT_PROTOCOL_VERSION_v5);
	nng_mqtt_msg_set_packet_type(connection_message, NNG_MQTT_CONNECT);
	nng_mqtt_msg_set_connect_clean_session(connection_message, true);
	nng_mqtt_msg_set_connect_keep_alive(connection_message, 20);

	property *properties = mqtt_property_alloc();

	mqtt_property_append(properties,
	    mqtt_property_set_value_str(ASSIGNED_CLIENT_IDENTIFIER, "client",
	        strlen("client"), false));
	mqtt_property_append(properties,
	    mqtt_property_set_value_u32(SESSION_EXPIRY_INTERVAL, 0));
	mqtt_property_append(
	    properties, mqtt_property_set_value_u16(TOPIC_ALIAS_MAXIMUM, 0));

	nng_mqtt_msg_set_connect_property(connection_message, properties);

	return connection_message;
}

#define TOPIC "test_topic"

void
Subscribe(nng_socket *socket)
{
	printf("Subscribing\n");

	nng_mqtt_topic_qos topic_qos = {
          .topic =
              {
                  .length = strlen(TOPIC),
                  .buf = (uint8_t *)TOPIC,
              },
          .qos = 0,
          .nolocal = 0,
          .rap = 0,
          .retain_handling = 0,
      };

	nng_msg *const subscribe_message = AllocateMessage();
	nng_mqtt_msg_set_packet_type(subscribe_message, NNG_MQTT_SUBSCRIBE);

	nng_mqtt_msg_set_subscribe_topics(subscribe_message, &topic_qos, 1);

	SendMessage(subscribe_message, socket);
}

int
OnConnect(void *rmsg, void *arg)
{
	printf("Connected to MQTT broker! %p\n", rmsg);

	nng_socket *socket = (nng_socket *) arg;
	Subscribe(socket);
	return 0;
}

int
OnDisconnect(void *rmsg, void *arg)
{
	printf("Disconnected from MQTT broker! %p\n", rmsg);
	return 0;
}

int
OnReceiveMessage(void *rmsg, void *arg)
{
	nng_msg *message = (nng_msg *) rmsg;

	printf("Received a message! %p\n", (void *) message);

	return 0;
}

int
OnSendMessage(void *rmsg, void *arg)
{
	nng_msg *message = (nng_msg *) rmsg;

	printf("Message sent successfully! %p\n", (void *) message);

	return 0;
}

int
main(int argc, char **argv)
{
	nng_socket  socket;
	const char *url = "mqtt-quic://127.0.0.1:14567";

	conf_quic config_user = {
        .tls =
            {
                .enable = false,
                .url = "",
                .cafile = "",
                .certfile = "",
                .keyfile = "",
                .ca = "",
                .cert = "",
                .key = "",
                .key_password = "",
                .verify_peer = true,
                .set_fail = true,
            },
        .qos_first = false,  // We might consider to bump it to true when using QoS > 0
        .multi_stream = false,
        .qkeepalive = 10,         // QUIC keepalive (seconds)
        .qconnect_timeout = 30,   // QUIC handshake idle timeout (seconds)
        .qdiscon_timeout = 1,    // QUIC disconnect timeout (seconds)
        .qidle_timeout = 30,      // QUIC disconnect after idle (seconds)
        .qcongestion_control = 1  // 0: cubic, 1: bbr
    };

	int outcome;
	outcome = nng_mqttv5_quic_client_open_conf(&socket, url, &config_user);
	if (outcome != 0) {
		fprintf(stderr, "Failed to open MQTT client socket: %s\n",
		    nng_strerror(outcome));
		return 1;
	}
	outcome = nng_mqtt_quic_set_connect_cb(&socket, OnConnect, &socket);
	if (outcome != 0) {
		fprintf(stderr,
		    "Failed to set MQTT client connect callback: %s\n",
		    nng_strerror(outcome));
	}
	outcome =
	    nng_mqtt_quic_set_disconnect_cb(&socket, OnDisconnect, &socket);
	if (outcome != 0) {
		fprintf(stderr,
		    "Failed to set MQTT client disconnect callback: %s\n",
		    nng_strerror(outcome));
	}
	outcome =
	    nng_mqtt_quic_set_msg_recv_cb(&socket, OnReceiveMessage, &socket);
	if (outcome != 0) {
		fprintf(stderr,
		    "Failed to set MQTT client message received callback: "
		    "%s\n",
		    nng_strerror(outcome));
	}
	outcome =
	    nng_mqtt_quic_set_msg_send_cb(&socket, OnSendMessage, &socket);
	if (outcome != 0) {
		fprintf(stderr,
		    "Failed to set MQTT client message send callback: "
		    "%s\n",
		    nng_strerror(outcome));
	}

	nng_msg *const connection_message = BuildConnectionMessage();
	SendMessage(connection_message, &socket);

	nng_msleep(5000);

	printf("Closing socket\n");
	nng_close(socket);

	printf("Done\n");
}

// NOLINTEND

Expected behavior
The program:

  • Connects to the broker
  • Subscribes to test_topic
  • Gently quits

Actual Behavior
The program:

  • Connects to the broker
  • Subscribe to test_topic
  • Sometimes holds infinitely on Closing socket

To Reproduce

  • Build the file
  • Run:
while true do; ./build/demo/dummy_quic/dummy_quic; done

After few iterations, the program will block on nng_close.

Output:

Message sent successfully! (nil)
Connected to MQTT broker! 0x73d06c000b70
Subscribing
Message sent successfully! (nil)
Closing socket

Environment Details

  • NanoSDK/NNG version: using NanoSDK main 6cb73c84
  • Operating system and version: Ubuntu 24
  • Compiler and language used: clang-20
  • Shared or static library: Static
  • Remote MQTT Broker info: EMQX 5.8.8

Additional context
Add any other context about the problem here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    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