I recently read this article by John Carbone about using UDP in embedded systems. The advice in the article is accurate, but there are some other issues to consider when designing UDP based protocols in embedded systems that Carbone did not address. I wanted to tackle them here.
First and foremost: UDP datagrams are trivial to forge because they are so simple. The header has the source port and address, destination port and address, a length, and a checksum. Consider what would happen if you were to trust the source port and address in your IoT device. An attacker could send your device packets from the server address and port you expect, and perform any action remotely that your network protocol allowed.
Because the source port and address are insufficient for authentication, you will need a secure authentication mechanism to ensure the packets are originating from a trusted source. One approach would be to use something like Datagram Transport Layer Security (DTLS)—the UDP equivalent of SSL/TLS. DTLS features full encryption and authentication. I have not seen it widely deployed yet, but it seems like it should be a best practice.
Maybe you think DTLS is too heavyweight or not available for your stack, and so you decide to roll your own authentication instead. An obvious approach is to do something simple like generating a random number for each request, sending that in a UDP packet to your cloud server, and then ensuring the reply UDP packet contains the correct random number. TCP uses the sequence number—basically the same approach—to avoid a similar attack. If you do this, please make sure your random number is large enough and random enough to actually afford protection. It took a couple tries for the TCP designers to get it right, so you should read RFC 6528 to understand the current state of the art. Keep in mind your embedded system will need to have enough entropy to actually do a good job at random number generation. Many systems don’t.
If you’re thinking of rolling your own encryption, stop. You won’t get it right.
Another major consideration in designing UDP protocols for the real world is preventing your device being used as a bandwidth amplifier for Distributed Denial of Service (DDOS) attacks. The way a DDOS attack works is, an attacker generates some kind of bogus UDP packet that results in the device sending a reply. The source address in the bogus packet is forged to be a system the attacker wants to overwhelm with traffic.
As an example, the UDP version of Network Time Protocol (NTP) was recently used in DDOS attacks, because it has a query command where the reply is 200 to 500 times larger than the query. The attacker flooded tens of thousands of NTP servers around the net, each of which then sent 500x as much traffic to the target. The key to preventing this type of attack is to design your protocol so that the reply from the device is always smaller than the request, even if that means your queries have to be padded with unused data.
Given the ease with which UDP packet headers can be forged, it is important to filter them correctly in your networking stack. In the original article, Carbone suggested using UDP between processors within an embedded product. This can be a good idea, but the external network interfaces on the product must be configured to filter out packets that arrive on an external interface but appear to be from or to an internal interface. Otherwise, you risk finding that your device has been “remotely controlled” or shut down in a denial of service attack.
While Carbone correctly claims UDP is faster than TCP, for nontrivial cases involving more than a single packet, it is difficult to make UDP perform as well as TCP if the receiver must acknowledge the sender. This is because TCP allows pipelining—that is, multiple outstanding unacknowledged packets from sender to receiver. If a UDP-based transport is sending acknowledgements for each packet received, there is 2x the latency time of the network added to the transmission time for each packet. If you’re going to allow pipelining, and then dealing with missing packets, and reordering packets to the sending order (which can be different than the receiving order), you’re on track to reinvent TCP, and you should just use that instead.
A final limitation to note with UDP and the IoT is that home routers and other common networking equipment have varying behaviors as far as allowing generic UDP packets in and out. You will have to deal with NAT and firewall traversal. Some of what you’ll have to deal with is described in this document by Google engineers for a UDP protocol they are designing.