Sniffing in PHP using libpcap: Thank you SWIG!
TweetThe motivation for a network sniffer written in PHP
Note: This article has been updated to include packet injection!
I've been wanting to try SWIG for a long time, but never got the chance, for one thing or the other. So this weekend I've finally decided to give it a try by trying to create a php extension that access a small C++ wrapper for libpcap, so I can sniff packets directly from PHP. Just for fun (and actually because I couldn't find any active pecl extension to use libpcap, so it might as well be something useful). I've named it "SimplePcap".
A little disclaimer: I'm of course not an expert in SWIG or the Zend Engine, so I might be missing something. If you spot anything that needs to be fixed, please drop me a line. Overall, I think it's a good sample to start toying around with SWIG. Also, this is not intended to be a deep insight into SWIG or anything like that. The SWIG manual is pretty damn good at it!
A couple of links:
- The complete source code for the extension is available at GitHub
- The documentation for tcpdump & libpcap
- The Online SWIG manual
- The PCAP man page, with pcap_t and pcap_*() function descriptions.
- You can get a linux x86_64 build at the CI server
What the SWIG? The library to rapidly develop PHP extensions in C/C++
For those of you who dont know what SWIG is: let's say you have a given C or C++ code, and you want or need to access it from a scripting language (let's call that, a "target language"). Well, SWIG is a tool that generates the necessary code to create an extension in lots of "target languages" (like PHP, Python, Java, Lua, Go, etc.), based on that particular piece of C/C++ code. The Online SWIG manual explains why it was born and all the available features and languages.
Basically speaking, you feed SWIG an "interface file" that tells swig what and how do you want to expose the C/C++ code to the scripting language, and then it will autogenerate the needed C/C++ files that will be the "glue" between your code and the target language.
SimplePcap (PHP side): List your network interfaces and sniff traffic from PHP Code
NOTE: A "complete" php sniffer that dumps the packets like tcpdump is included as an example in the GitHub repository for the source code.
This is how you can use SimplePcap from PHP to list all available devices:
To capture packets, first, get an instance of the SimplePcap class. You will need:
- The network interface name, can be something like "any", or "eth0"
- A libpcap filter, like "port 80". This tells libpcap to filter some specific traffic.
- The number of bytes to capture per packet, like 4096, 1024, 40, etc.
- The read timeout in milliseconds. This goes directly to pcap_open_live(). Use 0 for a blocking operation. If you get an exception saying that pcap_next() returned null, try incrementing this one, or setting it to 0.
To get the data sniffed on a given packet:
Also, you can get other packet information:
A sample output from running the included example
UPDATE: Inject forged packets in the network using only PHP
If you want to inject packets in the interface, you will be glad to know that I've added a method send() just for that:
An example file is provided, inject.php that when run, will inject the data contained in the given file. Here's a sample run sending an ICMP echo request captured with sniff.php:
SimplePcap (C++ side): How the C++ code that calls libpcap looks like
SimplePcap is actually pretty simple, since it was done only to try SWIG. There is a SimplePcap class that provides the core functionality and a Packet class that wraps around a pcap_t structure. Both are exposed to PHP by SWIG.
SimplePcap has a static method, findAllDevs() that calls pcap_findalldevs(), and returns a deviceList, which is nothing more than a standard C++ map<string, string>.
A typemap(out) exists (more on typemaps later) for deviceList, that will transform it to a php associative array, where the key is the name of the interface and the value is the interface description (pcap_t.name and pcap_t.description respectively).
To construct a SimplePcap class, you need the interface name (hence the need for findAllDevs()), a libpcap filter expression (like "host 127.0.0.1 and port 80"), the number of bytes to capture per packet, and the read timeout in milliseconds. If you get an exception saying that pcap_next() returned null, try incrementing this one, or setting it to 0.
Once you get the SimplePcap instance, the method get() can be used to get packets, which wraps around the pcap_next() function and returns objects of type Packet.
I did not use pcap_loop() intentionally, I thought it would be better here to let the php client have control. Besides, I would like to have Closures passed as callbacks when using pcap_loop(), and I haven't done anything yet towards that goal. Maybe for the next article! Also, I couldn't find out how to translate the exceptions from C++ to PHP exceptions (the ones you see here, which are custom SimplePcap exceptions, and not mappable into SWIG_* exceptions).
UPDATE: You can also use the method send() to inject packets in the interface, it will call pcap_inject().
The SWIG interface file
Here's the interface file I'm using for the extension.
You will notice code between %{ ... %}. The code between those tokens will be inserted directly into the source file generated by SWIG. Right below, a couple of SWIG files are included, mostly to have the typemaps shipped with SWIG. And right below those, there's a custom typemap I've made.
Typemaps
A typemap is code used to transform data back and forth from the target language to C/C++. Data types like strings, integers, floats, are already supported by SWIG and will be translated if you include the corresponding swig files for the language you're using (in this case, all the *.i files) you see below the %{...%} block.
The typemap(out) for deviceList specifies how SWIG will translate a deviceList into something usable from PHP, like an associative array (in this case). So that code snippet is used to return the associative array to PHP, where the key is the interface name and the value is the interface description coming in directly from libpcap.
Conclusions: Using SWIG to write a network sniffer with C++, libpcap and PHP and loving it!
SWIG is really great. I just did some C++ code and then worried about how to integrate it to PHP. Althought it seems that you really need lots of experience with it to actually do more advanced things in the right way. For example, I could not find out yet how to throw my custom C++ exceptions right into PHP (don't know if that's possible at all).
Also, I had to give default values for the Packet constructor, because the PHP code generated by SWIG (the proxy classes), would call the Packet constructor with just 1 argument.
And I'd like to expose properties along with methods (saw some pythong examples, but nothing really for php).
So I guess that sometimes it's more productive to just make the PHP extension than using SWIG. But if you want your code to be run in many languages, this is definitely an excellent library to try!