Using MQTT and Google’s Protocol Buffers to implement efficient RPCs


Introduction
Over the decades there were many attempts — some more successful than others — to implement RPCs (Remote Procedure Calls) in a programming language and network agnostic way.
SOAP (Simple Object Access Protocol), REST (Representational State Transfer), CORBA (Common Object Request Broker Architecture), JSON (JavaScript Object Notation), to name a few, are some protocols and architectures created with that purpose in mind.
In this article, I will describe our team experience using a combination of two relatively recent technologies to accomplish an efficient implementation of RPCs, derived from work developed by my colleague Julie Vogelman.
MQTT and Protobufs
MQTT
MQTT was authored by Andy Stanford-Clark (IBM) and Arlen Nipper (Cirrus Link, then Eurotech) in 1999. It was standardized in 2013 by OASIS.
MQTT defines a lightweight publish/subscribe protocol designed for resource-constrained devices and slow network links.
MQTT uses a broker that allows devices to publish data octets to topics, as well as subscribe to topics and receive data published to them.
Topics are strings that look like a path to a folder or file:
/this/is/an/example/of/a/topic
and are not predefined; a device simply publishes to a topic and any device that knows it can subscribe to receive data from it.
MQTT also allows devices to subscribe using wildcards. There are 2 wildcard symbols: ‘+’ and ‘#’
So if a device publishes to topics /this/is/an/example/of/a/topic and /this/is/an/example/of/another/topic, a subscriber can receive messages from both topics in 2 ways:
- Subscribe to /this/is/an/example/of/+/topic
- Subscribe to /this/is/an/example/of/#
Another feature of MQTT brokers that made them attractive for us is that they support both UDP and TCP, as well as one-way and two-way TLS.
In order to use MQTT developers can take advantage of open source libraries like Paho, which already support C and Java with Lua, Python, C++ and JavaScript on the way; and free, open source brokers like Eclipse mosquitto.
Protocol Buffers
Google initially developed protocol buffers — protobufs for short — in 2001 for internal use; it was made public in 2008.
Protobuf specifies a language-neutral mechanism to serialize data in a simple structure that is efficient for transmission between devices.
Its use is relatively simple: the developer defines a file with .proto with the data structure definition, like this:
message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;
required string callbacktopic = 4;}
Then use language generators provided by Google to create the serialization code for your programming language of choice. Currently Google supports C++, C#, Dart, Go, Java and Python.
Putting it all together
Here is a simple example that illustrates how it all gets glued together. The topic will look like the classpath for a Java class but it’s just our choice.
For this example, besides the Person message above we will define the Result message:
message Result { required int resultcode = 1;}
Client on a device in C++
- Subscribes to topic /com/myclient/MainClass/setPersonResult
- Defines the Person message in the messages.proto file
- Defines the Result message in messages.proto file
- Generates the C++ code to serialize/deserialize the messages
Server application in Java
- Defines the same Person message as the client
- Defines the same Result message as the client
- Generates the Java code to serialize/deserialize the messages
- Subscribes to topic /com/myserver/MainClass/+, so it can receive method calls for all methods in this class
Flow
- Client invokes the generated methods with the appropriate parameters; in particular, callbacktopic set to “/com/myclient/MainClass/setPersonResult”
- Client publishes the serialized buffer to the topic /com/myserver/MainClass/setPerson
- Server receives the data, deserializes and processes it by invoking method MainClass.setPerson (in this case we took advantage of Java’s Reflection and got the class and method straight from the topic)
- Once processed, server invokes the generated methods to serialize the result and publishes to /com/myclient/MainClass/setPersonResult
- Client receives the result, invokes the methods to deserialize it and calls the appropriate method to handle the result; in this case, because the client is in C++, we relied on a hash table to map topics to method pointers.
This schema scaled up well to all the API calls; the broker hides the internals of the server app, which can be protected behind VPN, firewalls or containers (we used Kubernetes and Docker to isolate the server app by the way).
Also the broker can handle thousands of messages per second, so no problem there.
It works really well, however…
However…
The same features that make it so flexible and efficient also make it very vulnerable. Here are some issues:
- What if several instances of the client use the same topic for the callback?
Well, it is a problem because they will all receive the responses sent to all clients. Not good, each client must receive the result for its own method call.
The solution is to add a unique identifier to the string so the callback topic is unique to the client. Something like:
/com/myclient/Th8858Hhjhkjhcvb&uAA34/MainClass/setPersonResult
2. What if an attacker subscribes to MQTT for all topics, using topic ‘#’?
This is a big problem. If the MQTT interface is exposed publicly — via URL or public IP address — then an attacker can easily eavesdrop all traffic between all clients and the server. A HUGE no-no.
We envisioned 4 possible solutions for this problem:
- The client and server negotiate an encryption key — for example, using Diffie-Hellman — and use it to encrypt the content before sending it to the topic; anyone eavesdropping will see the content but will not be able to read it
- Whitelisting IP addresses. Only whitelisted IP addresses could connect to the MQTT broker. Works but it is cumbersome to maintain.
- Use VPN. Access to the MQTT interface requires VPN so only clients with the credentials can connect. Works well if access is restricted to a client app or if users are required to register for using the app; not so much for an open, public API
- Use a broker implementation that offers Access Control. Normally these are commercial paid brokers, not free.
So as you can see, these solutions work but are not great. So, in conclusion…
Conclusion
Using MQTT and Protobufs for implementing RPCs works really well. It is efficient, easy to implement, fast and reliable.
But security is a concern; it is not a solution for every RPC application. For public APIs there are other approaches that are more secure and therefore preferred.
If however your use case is restricted to communication between your client application and your server, then it is a solution that worked well for us and you may want to check it out.