cazure-iot-hubazure-iot-sdk

How can I set the MQTT keepalive in the Azure IoTHub C SDK?


I'm trying to set up an Azure IotHub client with the C SDK, and I noticed that if I don't send out a new MQTT message every 10 seconds, the connection to the Hub is closed. I'd like to increase this timeout, and I think the option I need is "keepalive" (OPTION_KEEP_ALIVE, see this doc page). However, if I try to do

IOTHUB_DEVICE_CLIENT_LL_HANDLE device_ll_handle;
// [...]
int keepalive = 20;
IoTHubTransport_MQTT_Common_SetOption(device_ll_handle, OPTION_KEEP_ALIVE, &keepalive);
// [...]
device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(connection_string, iothub_transport)

in the code opening the device handle, I end up with a SEGFAULT, because it detects the connection as already open, and it tries to disconnect with DisconnectFromClient(transport_data). I can't find in the doc how to set this option: where should I set it? Thanks!


EDIT a MWE is (adapted from a sample, anonymized):

typedef struct CLIENT_SAMPLE_INFO_TAG
{
  PROV_DEVICE_LL_HANDLE handle;
  unsigned int sleep_time_msec;
  char* iothub_uri;
  char* device_id;
  bool registration_complete;
} CLIENT_SAMPLE_INFO;

PROV_DEVICE_TRANSPORT_PROVIDER_FUNCTION prov_transport;

CLIENT_SAMPLE_INFO azure_init() {

  CLIENT_SAMPLE_INFO user_ctx;

  (void)IoTHub_Init();
  (void)prov_dev_security_init(SECURE_DEVICE_TYPE_X509);

  memset(&user_ctx, 0, sizeof(CLIENT_SAMPLE_INFO));

  // Protocol to USE
  prov_transport = Prov_Device_MQTT_Protocol;

  user_ctx.registration_complete = false;
  user_ctx.sleep_time_msec = 10;

  LOG(INFO) << "Provisioning API Version: " << Prov_Device_LL_GetVersionString();
  LOG(INFO) << "Iothub API Version: " << IoTHubClient_GetVersionString();

  return user_ctx;
}

IOTHUB_DEVICE_CLIENT_LL_HANDLE open_iothub(const std::string &connection_string,
                                           const CLIENT_SAMPLE_INFO &azure_ctx,
                                           const std::string &x509_certificate,
                                           const std::string &x509_key) {
  const CLIENT_SAMPLE_INFO user_ctx{azure_ctx};
  IOTHUB_DEVICE_CLIENT_LL_HANDLE device_ll_handle;

  // Protocol to USE
  iothub_transport = MQTT_Protocol;

  // int keepalive = 20;
  // IoTHubTransport_MQTT_Common_SetOption(device_ll_handle, OPTION_KEEP_ALIVE, &keepalive);

  LOG(INFO) << "Creating IoTHub Device handle";  
  if ((device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(connection_string.c_str(), iothub_transport) ) == NULL)
    {
      LOG(ERROR) << "failed create IoTHub client " << user_ctx.iothub_uri << "!";
      return nullptr;
    }
  else
    {
      iothub_info.stop_running = false;
      iothub_info.connected = false;

      (void)IoTHubDeviceClient_LL_SetConnectionStatusCallback(device_ll_handle, iothub_connection_status, &iothub_info);

      // Set any option that are necessary.
      // For available options please see the iothub_sdk_options.md documentation
      bool traceOn = true;
      IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_LOG_TRACE, &traceOn);
      int keepalive = 20;
      IoTHubTransport_MQTT_Common_SetOption(device_ll_handle, OPTION_KEEP_ALIVE, &keepalive);

      //Setting the auto URL Encoder (recommended for MQTT). Please use this option unless
      //you are URL Encoding inputs yourself.
      //ONLY valid for use with MQTT
      bool urlEncodeOn = true;
      (void)IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_AUTO_URL_ENCODE_DECODE, &urlEncodeOn);

      // Set the X509 certificates in the SDK
      if (
          (IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_X509_CERT, x509_certificate.c_str()) != IOTHUB_CLIENT_OK) ||
          (IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_X509_PRIVATE_KEY, x509_key.c_str()) != IOTHUB_CLIENT_OK)
          )
        {
          LOG(ERROR) << "failure to set options for x509, aborting";
        }

        (void)IoTHubDeviceClient_LL_SetMessageCallback(
            device_ll_handle, receive_msg_callback, &iothub_info);
        LOG(INFO) << "Opened IoTHub connection via connection string " << connection_string;
      return device_ll_handle;
    }  
}

int main() {

    auto azure_connection = azure_helpers::azure_init();

    auto iot_handle = azure_helpers::open_iothub("HostName=XXXXXXXXXXXXXX.azure-devices.net;DeviceId=test2;x509=true",
                                                 azure_connection,
                                                 x509certificate, // read from file
                                                 x509privatekey); // read from file

}

Output:

Provisioning API Version: 1.14.0
Iothub API Version: 1.14.0
Creating IoTHub Device handle
Error: Time:Tue Jan 14 15:41:58 2025 File:/home/abertulli/<project_root>/azure-iot-sdk-c/iothub_client/src/iothub_client_authorization.c Func:IoTHubClient_Auth_Get_Credential_Type Line:361 Invalid Parameter handle: (nil)
Segmentation fault

Solution

  • "segmentation fault" This error comes because you are calling IoTHubTransport_MQTT_Common_SetOption() on an uninitialized handle. This function is internal to the Azure IoT C SDK and should not be used directly.

    IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_KEEP_ALIVE, &keepalive);
    

    Modified Code:

    LOG(INFO) << "Creating IoTHub Device handle";  
    if ((device_ll_handle = IoTHubDeviceClient_LL_CreateFromConnectionString(connection_string.c_str(), iothub_transport)) == NULL)
    {
        LOG(ERROR) << "failed to create IoTHub client " << user_ctx.iothub_uri << "!";
        return nullptr;
    }
    else
    {
        iothub_info.stop_running = false;
        iothub_info.connected = false;
    
        (void)IoTHubDeviceClient_LL_SetConnectionStatusCallback(device_ll_handle, iothub_connection_status, &iothub_info);
    
        // Enable MQTT Keep-Alive  
        int keepalive = 20;
        if (IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_KEEP_ALIVE, &keepalive) != IOTHUB_CLIENT_OK)
        {
            LOG(ERROR) << "Failed to set keep-alive option!";
        }
    
        // Enable logging  
        bool traceOn = true;
        IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_LOG_TRACE, &traceOn);
    
        //Setting the auto URL Encoder (recommended for MQTT). Please use this option unless  
        //you are URL Encoding inputs yourself.  
        //ONLY valid for use with MQTT  
        bool urlEncodeOn = true;
        (void)IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_AUTO_URL_ENCODE_DECODE, &urlEncodeOn);
    
        // Set the X509 certificates in the SDK  
        if ((IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_X509_CERT, x509_certificate.c_str()) != IOTHUB_CLIENT_OK) ||
            (IoTHubDeviceClient_LL_SetOption(device_ll_handle, OPTION_X509_PRIVATE_KEY, x509_key.c_str()) != IOTHUB_CLIENT_OK))
        {
            LOG(ERROR) << "Failure to set options for x509, aborting";
        }
    
        (void)IoTHubDeviceClient_LL_SetMessageCallback(device_ll_handle, receive_msg_callback, &iothub_info);
        LOG(INFO) << "Opened IoTHub connection via connection string " << connection_string;
        return device_ll_handle;
    }
    

    As Document states:

    You should set the options you need right after creating your IoT Hub device or module handle. Setting most options after the connection has been initiated may be silently ignored or applied much later.

    "Right after" means immediately after creation, NOT before. If you set options before creating the handle, it results in undefined behavior (segfault in the case).