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
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:
- Add a
IO.puts inspect(data)on line 14 of the
- Start your application with
mix run --no-halt
- 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
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
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.
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