Inter-module messaging without IoT Edge routing

Azure IoT supports cloud-to-device messaging with Direct Methods. This is an important tool when you want to control your devices real-time or if you want to execute logic on the device real-time.

In the past I have written about Direct Methods a couple of times. like this blog. I then wrote about IoT Edge supporting Direct Methods too. To be more specific, Modules in an IoT Edge support Direct Methods.

But my colleague Heindirk pointed me at a little gem unknown to me.

The same logic used to communicate from Azure cloud to a module can also be used to communicate from one module to another module without IoT Edge routes!

This makes several new IoT strategies possible!

Let’s check out this gem.

Building a test module

What we need to test this, is:

  1. An IoT Edge device with two modules named ‘sourceModule’ and ‘targetModule’
  2. No IoT Edge routing set between these modules
  3. A trigger. I use a C2D call to the ‘sourceModule’
  4. Module ‘sourceModule’ calls a direct method on ‘targetModule’
  5. The response of the target has to be sent back to the cloud by the source

So I added this logic into a new IoT Edge module :

class Program
{
    private static string _deviceId; 
    private static string _moduleId; 

    ....
    static async Task Init()
    {
        MqttTransportSettings mqttSetting = new MqttTransportSettings(TransportType.Mqtt_Tcp_Only);
        ITransportSettings[] settings = { mqttSetting };

        // Open a connection to the Edge runtime
        ModuleClient ioTHubModuleClient = await ModuleClient.CreateFromEnvironmentAsync(settings);
        await ioTHubModuleClient.OpenAsync();

        _deviceId = System.Environment.GetEnvironmentVariable("IOTEDGE_DEVICEID");
        _moduleId = System.Environment.GetEnvironmentVariable("IOTEDGE_MODULEID");

        Console.WriteLine($"IoT Hub module client {_deviceId} {_moduleId} initialized.");

        await ioTHubModuleClient.SetMethodHandlerAsync("InterDirectMethod", MethodModule, ioTHubModuleClient);
    }

    private async static Task<MethodResponse> MethodModule(MethodRequest methodRequest, object userContext)
    {
        Console.WriteLine($"{_deviceId} {_moduleId} - Method request InterDirectMethod received at {DateTime.Now}");

        ModuleClient ioTHubModuleClient = (ModuleClient)userContext;

        if (_moduleId != "targetModule")
        {
            var request = new MethodRequest("InterDirectMethod", methodRequest.Data);
            var responseS = await ioTHubModuleClient.InvokeMethodAsync(_deviceId, "targetModule", request).ConfigureAwait(false);

            Console.WriteLine($"{_deviceId} {_moduleId} - Method response received from TargetModule at {DateTime.Now}");

            return responseS;
        }
        else
        {
            // moduleId == "targetModule"
            var requestText = Encoding.UTF8.GetString(methodRequest.Data);
            Console.WriteLine($"method request received: {requestText}");
            var json = "{ \"response\": \"%1\"}".Replace("%1", $"from {_deviceId} {_moduleId} at {DateTime.Now}");
            var response = new MethodResponse(Encoding.UTF8.GetBytes(json), 200);

            return response;
        }
    }
}

This module can respond to a Direct Method named ‘InterDirectMethod’.

If the call is coming from the cloud to the first module (named anything but ‘targetModule’), the same call is send as a new Direct Method to the second module.

Testing the module

We start with configurating an IoT Edge device with two modules:

Same modules are based on the same image (svelde/testdirectmodule:0.0.3-amd64):

And to make clear, there is no routing involved between the modules:

Both modules are now initialized. Everything is set.

We send a Direct Method to the ‘sourceModule’:

This Direct Method is indeed received by the ‘sourceModule’:

And as you can see, the source module sends this ‘request’ to the target module and got a response back.

We can see this in the logging of the target module too:

The target module receives the request and shows it in the log.

So yes, we can send a Direct Method from one module to another module.

Note: because both modules are living in the same IoT Edge device, the message is handled by the edgeHub module probably.

Finally, we see the appearance of the response of the target module in the Azure portal:

Limit the scope

Because the method allows to pass the device name too in the request, in theory it could be possible to communicate from one device to another too!

If this is possible, it’s not recommended to make use of this.

If one device is compromised, a hacker could freely ‘talk’ to other devices in your swarm of IoT devices. This could potentially lead to shutting down all your devices (with ddos attacks) or worse.

Conclusion

This opens new scenarios using this alternative communication channel. There are a few things to take into account:

  1. This message solution is not configurable or traceable in regular IoT Edge routing
  2. It’s a Direct Method message. These message are not buffered. The target must be up and running and communicating to get this working
  3. A direct relationship between modules is set. Module A needs to know the exact name of module B to make this working. This can cause some administration stress.

Share how you are using this gem below.