code

In your (clandestine) consulting work for ACME Spy Corporation, you’ve been tasked with the following:

  • Listen for UDP packets on port 21337
  • Parse said messages according to the specification
  • Log the message contents for later review

The specification of each message is as follows:

Message Header, 30 bytes
Message Body, 45 bytes
  - Priority Code, 1 byte, string
  - Agent Number, 4 bytes, unsigned integer
  - Message, 40 bytes, string

Bytes are in little-endian format.

Let’s Get Started

First, acquaint yourself with Elixir. Then when you’re ready, make a new project with Mix:

mix new acme_udp_logger
cd acme_udp_logger

Implement the Application and Supervisor patterns in the AcmeUdpLogger module. This will allow us to supervise the code that will handle the UDP packets. If that code crashes, we can restart it atomically (a great feature of Elixir, by the way).

You will also have to add your application to the mix.exs file, within the application/0 function.

Listening for UDP Packets

Elixir makes it very easy to start listening for UDP packets. You’ll need to use the :gen_udp Erlang module.

First we will need a module that will handle this task for us. Let’s call it MessageReceiver. This module should implement GenServer, so it can be supervised by the application and run on its own process.

Don’t forget to add this module to the list of supervised children in AcmeUdpLogger (line 7):

At this point, you can test to see if everything is setup correctly:

  1. Add a IO.puts inspect(data) on line 14 of the MessageReceiver
  2. Start your application with mix run --no-halt
  3. In a separate terminal session, use netcat to send UPD packets to localhost, port 21337 nc -u 127.0.0.1 21337. After you run this command, netcat will allow you to send messages via UDP by simply typing some text and hitting Enter. You should see the logged message appearing in your first terminal session (running mix run).

Parsing UDP with Binary Pattern Matching

Okay, here’s the good stuff. In the first handle_info/2 function of MessageReceiver, we will parse the UPD binary data using pattern matching, and log it to the console using Logger:

Here is what a test for the above would look like: message_receiver_test.exs.

In the binary pattern matching block (« » ), the left side is the variables I am assigning from values on the right side. The order of the expressions corresponds to the order of the bytes in the message. You can see how nicely this lines up with the specification at the beginning of this post. For more on bitstring/binary pattern matching syntax, check the Elixir documentation here.

Getting Ready for Prime Time

Before deploying code like this into production (with potentially massive amounts of incoming packets), I would recommend separating the receiving and parsing code. You will likely want to have a pool of parsers with a library like poolboy. The receiver would hand off a packet to an available parser, which would process it asynchronously. This would prevent backups from occurring if the parsing code takes too long (or crashes). You should also check the length of each packet to see if it’s the length your code expects (otherwise pattern matching will fail), and handle invalid/irrelevant packets appropriately.

For expert help, drop us a line.

Wrapping Up

I hope this primer has been helpful to you in your UDP parsing endeavors. I think that Elixir is a great language for this sort of work, with its tasteful syntax and highly functional process architecture.

If you have any questions, feel free to DM me on Twitter @civilframe

Code for this guide: https://github.com/civilframe/acme-udp-logger