Wednesday, December 14, 2011

Signals and sockets for querying a process

Hello again, dear reader!

Another part of the research I'm doing entails capturing, processing, and storing network packet attributes. This is done in a nifty application that invol... oh, but that is a post on its own! What I'd like to share today is an interesting little way of sharing data between the packet capture process and another running process.

So here's the skinny: my application uses libpcap to do packet capture. Pcap has a couple ways to process the packets it grabs off the wire, both of which are blocking. My code also has to answer queries from a single other process on the same machine. But, even if my while loop (if using pcap_next()) or callback (if using pcap_loop() or pcap_dispatch()) checks somehow for pending queries, the querying process has to wait until the pcap process gets another packet for that check to occur. The question arises: how can this application respond immediately to a query, regardless if packets are currently being captured?

Shared memory and multithreading is an option, as is pushing data to a separate database. But we want simple (my entire application is under 300 lines of code, counting the solution I describe here), and the machines I want to run this code on may not be able to support a database server. Besides, what's the fun in doing this if there isn't an opportunity for a bit of hackery?

It turns out that a combination of sockets and signals does just the trick. We're going to give the pcap process a listening Unix socket and and a function to handle signals, and let the OS do the rest of the work for us.

Before we jump into the code, let's making life simpler and take all this packet capture business out of the picture - that's complicated enough on its own, and may be the subject of another post in the future. Instead, let's say we have a table (2-D array) of students and the classes they must take. Each spot in the table is a struct with the quarter in which they took the class and the grade they received. That way we get a struct for the query (student and class) and another for the response (quarter and grade). And to keep things easy on ourselves, we'll make everything a number except for the grade, which will be a single character ('A', 'B', 'C', and so on).

Let's look at the code that processes a query (all error-checking has been removed for simplicity):

  void handle_query(int sig) {
    char buffer[BUF_SIZE];
   
    int sd = accept(sock, NULL, NULL);
    int len = recv(sd, buffer, BUF_SIZE, 0);
    struct query *q = (struct query *)buffer;
   
    struct record *r = &records[q->student][q->class];
   
    send(sd, (char *)r, sizeof(struct record), 0);
    close(sd);
  }

Wow, that was easy! Looks a lot like the standard TCP server from a network programming 101 class, doesn't it? Accept a connection from a listening socket, receive a query, typecast it into a struct, do a lookup, send the result typecast as a byte array, and close the connection. If you haven't seen something similar before, check this out or do a quick Google search for "Linux TCP server in C". I'll provide the definitions of struct query and struct record at the end; for now, just know that sock and records are global variables.

So what's with this funky-looking function declaration? It's a void; that's okay, but what's this int sig that never gets used in the function body? Well, this function isn't actually called by any code in the program per se; it's a signal handler. "A signal what?" you ask...

Sunday, December 4, 2011

Sending raw Ethernet frames in 6 easy steps

Part of my work entails building a protocol stack in C that lives alongside TCP/IP and which is constructed entirely in user space, save for a few standard system calls. Hence, I need the ability to craft raw Ethernet frames and send them using only the facilities the operating system provides.

Fortunately, I have two things going for me. First, the implementation is all being done under Linux (in particular, a 2.6.32 kernel, though I believe any 2.6.x kernel will do). Second, a few other authors have gone before me in deciphering the man pages and system calls and have put together some great example codes. Most notably, Andreas Schaufler, whose writeup inspired my implementation as well as the code in this post.

So why write more on the subject? Because it takes time to understand all the moving parts and pieces, and most of us are operating under deadlines. What I hope to contribute is a one-stop, soup-to-nuts explanation with example code to save time for the next person. I'm going to assume you are somewhat comfortable with the C programming language and have seen or done some network programming before. However, my goal is to make this as painless as possible.

Starting point

First, let's try to make things easy for whomever is using this code. If they want to send a raw Ethernet frame, what things can we reasonably ask them to provide?
  • The destination MAC address would be nice, for sure. If they only know the IP, you can either have them look up the MAC with the arp command or code that lookup into your software. Let's assume you've done this if needed.
  • Since the frame needs to go out some particular Ethernet interface, we'll assume they know this already and can specify it by its "friendly" name, e.g., "eth0". The source MAC is needed as well, but we can look that up from the interface, assuming they're not trying to fake it. If they are, I will point out where to do this.
  • An Ethernet protocol number would also be good to know. This is the value that tells the receiving system which protocol is contained inside the frame. IP uses 0x0800, but perhaps you, like me, want to craft your own protocol that isn't processed by the TCP/IP stack.
  • Finally, the bytes you want to put into the message. This part is entirely up to you. Maybe you want to inject the contents of a captured packet, or some payload of your own creation. All we care about is that the bytes are stored somewhere (we'll say a C byte or "char" array) and you know the number of bytes to be sent. (We'll also assume you're staying within the number of bytes that fit in a frame, a.k.a. the Maximum Transmission Unit or MTU.)
Given these four pieces of information, let's walk through the code to send a frame. It will take six steps, listed below. Depending on your starting point, you may omit some of these steps.
  1. Defining our input and output data
  2. Building a raw "packet" socket
  3. Looking up the interface index and MAC address
  4. Filling in the packet contents
  5. Filling in the link layer socket address structure
  6. Sending the packet

Greetings!

Hello, dear readers! To kick off this blog, I thought I would share some of the technical bits from the past several months of working on my dissertation research.

In my field (computer science), underpinning most academic papers is a mountain of prototyping work that often does not see the light of day. Pity that, after giving up on man pages as a complete source of documentation and digging through forum posts and kernel code to uncover key pieces of information needed to make software work, that knowledge should never find a home.

But, lucky you! You have managed to stumble across this blog, and willingly or not, you have lent me your eyes and mind for the next few minutes, so I intend to make the most of it! (After all, how often does a grad student get a captive audience when talking about the nitty gritty of his or her work?)

Okay, okay, enough intro, let's get on with the good stuff!