TCP Listener on Microsoft Azure for IoT Devices

cloud-service-worker-role-automation-runbook.png

After working on the beacon projects back half a year ago, I was given a new task which is building a dashboard for displaying data collected from IoT devices. The IoT devices basically are GPS tracker with a few other additional sensors such as temperature and shaking detection.

I’m new to IoT field, so I’m going to share in this article what I had learnt and challenges I faced in this project so that it would benefit to juniors who are going to do similar things.

Project Requirements

We plan to have the service to receive data from the IoT devices to be on Microsoft Azure. There will be thousands or even millions of the same devices deployed eventually, so choosing cloud platform to help us scaling up easily.

We also need to store the data in order to display it on dashboard and reports for business use cases.

Challenge 1: Azure IoT Hub and The Restriction of Device Firmware

In the documentation of the device protocol, there is a set of instructions as follows.

First when device connects to server, module sends its IMEI as login request. IMEI is sent the same way as encoding barcode. First comes short identifying number of bytes written and then goes IMEI as text (bytes).

After receiving IMEI, server should determine if it would accept data from this module. If yes server will reply to module 01 if not 00.

I am not sure who wrote the documentation but I am certain that his English is not that easy to comprehend in the first read.

Anyway, this is a good indication that Azure IoT Hub will be helpful because it provides secure and reliable C2D (Cloud-to-Device) and D2C communication with HTTP, AMQP, and MQTT support.

However, when I further read the device documentation, I realized that the device could only send TCP packets over in a protocol the device manufacturer defined. In addition, the device doesn’t allow us to update its firmware at this moment, making it to send data using protocols accepted by Azure IoT Hub is impossible.

There is a fierce discussion about this on Stack Overflow. Unfortunately, none of the respondents understood what the OP was trying to say.

So, I have to say bye-bye to Azure IoT Hub and move on to build TCP Listener myself on Azure.

Challenge 2: Hosting TCP Listener on Azure

There is a great code sample on how to build a TCP listener in C# to listen for connections from TCP network clients.

So, where could we put this code at?

Could we use Azure App Service, such as Functions or Web Apps? Unfortunately, no. This is because only 80/TCP and 443/TCP are exposed publicly and the only protocol that works is HTTP. In addition, App Service is all IIS, the web server provides the entire platform, there is no room for long running processes or threads that can sit and wait for communication on another port outside of IIS.

The only easy option we have now is to use Azure Cloud Service with Worker Role. Worker Role does not use IIS and it can run our app standalone.

creating-worker-role.png
Creating a new Cloud Service project with one Worker Role on Visual Studio 2017.

A default template of WorkerRole class will be provided.

public class WorkerRole : RoleEntryPoint
{
    private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
    private readonly ManualResetEvent runCompleteEvent = new ManualResetEvent(false);

    public override void Run()
    {
        Trace.TraceInformation("TrackerTcpListener is running");

        try
        {
            this.RunAsync(this.cancellationTokenSource.Token).Wait();
        }
        finally
        {
            this.runCompleteEvent.Set();
        }
    }

    public override bool OnStart()
    { 
        // Set the maximum number of concurrent connections
        ServicePointManager.DefaultConnectionLimit = 12;

        // For information on handling configuration changes
        // see the MSDN topic at https://go.microsoft.com/fwlink/?LinkId=166357.

        bool result = base.OnStart();

        Trace.TraceInformation("TrackerTcpListener has been started");

        return result;
    }

    public override void OnStop()
    {
        Trace.TraceInformation("TrackerTcpListener is stopping");

        this.cancellationTokenSource.Cancel();
        this.runCompleteEvent.WaitOne();

        base.OnStop();

        Trace.TraceInformation("TrackerTcpListener has stopped");
    }

    private async Task RunAsync(CancellationToken cancellationToken)
    {
        // TODO: Replace the following with your own logic.
        while (!cancellationToken.IsCancellationRequested)
        {
            Trace.TraceInformation("Working");
            await Task.Delay(1000);
        }
    }
}

It’s obvious that the first method we are going to work on is the RunAsync method with a “TODO” comment.

However, before that, we need to define an IP Endpoint for this TCP listener so that we can tell the IoT device to send the packets to the specified port on the IP address.

worker-role-endpoints.png
Configuring Endpoints of a Cloud Service.

With endpoints defined, we can then proceed to modify the code.

private async Task RunAsync(CancellationToken cancellationToken)
{
    try
    {
        TcpClient client;

        while (!cancellationToken.IsCancellationRequested)
        {
            var ipEndPoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints["TcpListeningEndpoint1"].IPEndpoint;
            
            var listener = new System.Net.Sockets.TcpListener(ipEndPoint) { ExclusiveAddressUse = false };
            listener.Start();

            // Perform a blocking call to accept requests.
            client = listener.AcceptTcpClient();

            // Get a stream object for reading and writing
            NetworkStream stream = null;

            try
            {
                stream = client.GetStream();

                await ProcessInputNetworkStreamAsync(stream);
            }
            catch (Exception ex)
            {
                // Log the exception
            }
            finally
            {
                // Shutdown and end connection
                if (stream != null)
                {
                    stream.Close();
                }

                client.Close();

                listener.Stop();
            }
        }
    }
    catch (Exception ex)
    {
        // Log the exception
    }
}

The code for the method ProcessInputNetworkStreamAsync above is as follows.

private async Task ProcessInputNetworkStreamAsync(string imei, NetworkStream stream)
{
    Byte[] bytes = new Byte[5120];
    int i = 0;
    byte[] b = null;
    var receivedData = new List<string>();

    while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
    {
        receivedData = new List<string>();

        for (int reading = 0; reading < i; reading++)
        {
            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(bytes, reading, 1);
                b = ms.ToArray();
            }
            
            receivedData.Add(ConvertHexadecimalByteArrayToString(b));
        }

        Trace.TraceInformation("Received Data: " + string.Join(",", receivedData.ToArray()));

        // Respond from the server to device
        byte[] serverResponse = ConvertStringToHexadecimalByteArray("<some text to send back to the device>");
        stream.Write(serverResponse, 0, serverResponse.Length);
    }
}

You may wonder what I am doing above with ConvertHexadecimalByteArrayToString and ConvertStringToHexadecimalByteArray methods. They are needed because the packets used in the TCP protocol of the device is in hexadecimal. There is a very interesting discussion about how to do the conversion on Stack Overflow, so I won’t repeat it here.

Challenge 3: Multiple Devices

The code above is only handling one port. Unfortunately, the IoT device doesn’t send over the IMEI number or any other identification number of the device when the actual data pack is sent to the server. Hence, that means if there is more than one IoT device sending data to the same port, we will have no way to identify who is sending the data at the server side.

Hence, we need to make our TCP Listener to listen on multiple ports. The way I chose is to use List<Task> in the Run method as shown in the code below.

public override void Run()
{
    try
    {
        // Reading a list of ports assigned for trackers use
        ...

        var tasks = new List<Task>();
        
        foreach (var port in trackerPorts)
        {
            tasks.Add(this.RunAsync(this.cancellationTokenSource.Token, port));
        }
 
        Task.WaitAll(tasks.ToArray());
    }
    finally
    {
       this.runCompleteEvent.Set();
    }
}

Challenge 4: Worker Role Not Responding Irregularly

This turns out to be the biggest challenge in using Worker Role. After receiving data from the IoT devices for one or two days, the server was not recording any further new data even though the devices are working fine. So far, I’m still not sure about the cause even though there are people encountering similar issues as well.

Hence, I have to find a way to automatically restart the Worker Role for me. Thus, I decided to use PowerShell script to reboot the instance. There is a sample code on Microsoft Technet Gallery – Script Center which does similar thing.

I proceed to use Azure Automation which provides Runbooks to help handling the creation, deployment, monitoring, and maintenance of Azure resources. The Powershell Workflow Runbook that I use for rebooting the worker role daily is as follows.

workflow Reboot-CloudService
{
    Write-Output "Started!"
    
    $azureSubscriptionId = Get-AutomationVariable -Name "AzureSubscriptionId"
    $cloudServiceName = Get-AutomationVariable -Name "CloudServiceName"
    $workerRoleInstanceName = Get-AutomationVariable -Name "WorkerRoleInstanceName" 
    
    $myCredential = Get-AutomationPSCredential -Name "Chun Lin"
    Add-AzureAccount -Credential $myCredential
    
    Select-AzureSubscription -SubscriptionId $AzureSubscriptionId

    Write-Output "Restarting for cloud service: $cloudServiceName."

    ReSet-AzureRoleInstance -ServiceName $cloudServiceName -Slot "Production" -InstanceName $workerRoleInstanceName -Reboot

    Write-Output "Restarted successfully!"
}

In case you wonder where I defined the values for variables such as AzureSubscriptionId, CloudServiceName, and WorkerRoleInstanceName, as well as automation PowerShell credential, there are all easily found in the Azure Portal under “Share Resources” section of Azure Automation Account.

variables-and-credentials-in-automation.png
Providing credentials and variables for the Runbook.

After setting up the Runbook, we need to define schedules in Automation Account and then link it to the Runbook.

setting-schedules-for-automation.png
Setting up schedule and linking it to the Runbook.

There is another tool in the Azure Portal that I find it to be very useful to debug my PowerShell script in the Runbook. It is called the “Test Pane”. By using it, we can easily find out if the PowerShell script is correctly written to generate desired outcome.

test-pane.png
Test Pane available in Runbook.

After that, we can easily get a summary of how the job runs on Azure Portal, as shown in the following screenshot.

azure-automation.png
Job Statistics of Azure Automation.

Yup, that’s all what I had learnt in the December while everyone was enjoying the winter festivals. Please comment if you find a better alternative to handle the challenges above. Thanks in advance and happy new year to you!

References

Azure Cloud Service and SSL

I recently read an article written by Jeff Atwood on Coding Horror about whether we should encrypt all the traffic to our websites. I have a website which is utilizing external accounts with the help of .NET Identity, hence I must use HTTPs for my site before enabling users to login with their Facebook or Google accounts.

Purchase SSL Certificate from RapidSSL

My website is .NET web application with MVC 5 as front-end. It is also being hosted on Azure Cloud Service. For SSL certificate, I got it from RapidSSL.

RapidSSL Enrollment
RapidSSL Enrollment

Previously when I renewed the certificate for web application hosted on virtual machine, I could easily RDP to the virtual machine to configure its IIS settings. Now, the way to do it for Azure Cloud Service on the Azure Management Portal is a bit different.

Enter Certificate Signing Request (CSR)

In the process of purchasing SSL certificate on RapidSSL, I needed to submit CSR. To generate a CSR, what I did is just launching the IIS Manager on my Windows 8.1 machine. The process is pretty straightforward, as demonstrated on DigiCert website. The steps are as follows.

  1. Double click on “Server Certificates” feature of the local server;
  2. Under the “Action” panel, choose “Create Certificate Request…” link;
  3. Then there would be a window called “Distinguished Name Properties” popped out;
  4. Key in the correct information about the Common Name (which is the domain name of my website) and organization in the window;
  5. Choose “Microsoft RSA SChannel Cryptographic Provider” as the cryptographic service provider;
  6. Input “2048” as bit length.

CSR was generated successfully. I copied the generated text to RapidSSL textbox to continue the purchase.

Install SSL Certificate

After my payment went through, I received the certificate in text format via email from RapidSSL as shown below.

Web Server CERTIFICATE
----------------

-----BEGIN CERTIFICATE-----
<encoded data>
-----END CERTIFICATE-----

I then copied it to a text file and saved the file with the .cer extension.

Then, I went back to the IIS Manager on my computer. In the same Actions panel where I created the CSR, I then chose another option “Complete Certificate Request…”. In the new window, I provided the .cer file generated earlier.

Update Service Definition File

After that, in the Visual Studio Solution Window of my web project, I added a <Certificates> section, a new <InputEndpoint> for HTTPS, and a <Binding> element to map the HTTPS endpoint to my website within the WebRole section in the ServiceDefinition.csdef file.

<WebRole name="MyWebsiteWeb" vmsize="Medium">
    <Sites>
        <Site name="Web">
            <Binding>
                ...
                <Binding name="HTTPSEndpoint" endpointName="EndpointS" />
            </Bindings>
        </Site>
    </Sites>
    <Endpoints>
        ...
        <InputEndpoint name="EndpointS" protocol="https" port="443" certificate="SampleCertificate" />
    </Endpoints>
    ...
    <Certificates>
        <Certificate name="SampleCertificate" storeLocation="CurrentUser" storeName="My" />
    </Certificates>
</WebRole>

Update Service Configuration File

In addition, I edited the ServiceConfiguration.Cloud.cscfg file with one new <Certificates> section in the Role section.

<Role name="MyWebsiteWeb">
    ...
    <Certificates>
        ...
        <Certificate name="SampleCertificate" thumbprint="xxxxxx" thumbprintAlgorithm="xxx" />
    </Certificates>
</Role>

Both the thumbprint and thumbprintAlgorithm can be retrieved by double clicking on the .cer file.

Thumbprint and Its Algorithm
Thumbprint and its algorithm

Export Certificate as .pfx File

When I uploaded .cer file to Azure Management Portal, it couldn’t work. I had no idea why. Hence, I tried the alternative, which is using .pfx file. To do that, I first exported the certificate as .pfx file.

Firstly, I launched the Microsoft Management Console by running mmc.exe.

Export certificate from Microsoft Management Console.
Export certificate from Microsoft Management Console.

Secondly, I did the following steps to trigger the Certificate Export Wizard.

  1. File > Add/Remove Snap-in…
  2. Choose “Certificate” under “Available snap-ins” and then click “Add >”
  3. In the popup “Certificates snap-in” window, choose “Computer account”
  4. With the previous choice, I make snap-in to always manage in “Local computer”
  5. After clicking on the “Finish” button, I then click on the “Certificates” folder under “Personal” folder under “Certificates (Local Computer)” under the “Console Root”
  6. Right-click on the certificate that I want to export and choose export
  7. Finally the “Certificate Export Wizard” appears!

Finally, in the wizard, I followed the following steps to create a .pfx file of the certificate.

  1. Choose to export private key with the certificate
  2. Format will be Personal Information Exchange – PKCS #12 with all certificates in the certification path is included, if possible
  3. Enter a password to protect the private key
  4. Export
Certificate Export Wizard - Password and Private Key
Certificate Export Wizard – Password and Private Key

More detailed instructions can be found online, for example a page on Thawte about export a certificate from Microsoft IIS 7.

Upload Certificate to Azure

I then uploaded it to Microsoft Azure. It’s very simple. Just choose the cloud service and then upload the .pfx file (and enter the password used earlier for protecting the private key) to the certificate collection of the cloud service.

Upload certificate to Microsoft Azure in the Certificates tab.
Upload certificate to Microsoft Azure in the Certificates tab.

That’s all. It’s pretty straightforward, isn’t it?

If you would like to read more about Azure Cloud Service and SSL, please read the following articles which I find to be very useful.