Integrating Twisted with a pcap-based Python packet sniffer
Twisted is an awesome event-driven networking engine. Unfortunately, it does not have good support for interfacing with raw sockets (unlike its support for many network protocols, which is amazing). Anyway, I recently needed to work with raw sockets so I had to find a way to make it work with Twisted. Though Twisted does have a module (twisted.pair) which tries to provide some support for raw sockets, the module is poorly documented and requires a library which is not readily available.
I put together a short sample (see below) which shows how to capture raw packets alongside the main Twisted event loop. It would be trivial to extend this example to also write to a raw socket (using an ordinary Python socket). This example can also be downloaded here.
# This sample shows how to run a libpcap-based packet sniffer concurrently with # the Twisted framework. The Twisted component is an "Echo" TCP server # (listening on port 9999) which prints everything it receives. When a client # connects, it starts the pcap thread. When the pcap thread receives a packet, # it sends a message to the client telling it the size of the received packet. # Finally, when the client disconnects the program is terminated. # To try this contrived example out, run this script as root (so that it can use # pcap) and then connect to the echo server (e.g., telnet localhost 9999). Note # that the pcap parameters are hard-coded. This code uses twisted 8.0.2 and # pcapy-0.10.4. import os from pcapy import open_live from twisted.internet.protocol import Protocol, Factory from twisted.internet import reactor # pcap settings DEV = 'eth0' # interface to listen on MAX_LEN = 1514 # max size of packet to capture PROMISCUOUS = 1 # promiscuous mode? READ_TIMEOUT = 100 # in milliseconds PCAP_FILTER = '' # empty => get everything (or we could use a BPF filter) MAX_PKTS = -1 # number of packets to capture; -1 => no limit def run_pcap(f): # the method which will be called when a packet is captured def ph(hdr, data): print 'pcap heard: when=%s sz=%dB' % (hdr.getts(), len(data)) # thread safety: call from the main twisted event loop reactor.callFromThread(f, len(data)) # start the packet capture p = open_live(DEV, MAX_LEN, PROMISCUOUS, READ_TIMEOUT) p.setfilter(PCAP_FILTER) print "Listening on %s: net=%s, mask=%s" % (DEV, p.getnet(), p.getmask()) p.loop(MAX_PKTS, ph) # a silly echo server which prints what it receives and sends info about the # size of each packet captured on DEV class Echo(Protocol): def connectionLost(self, reason): os._exit(0) # kill the whole process def connectionMade(self): # run pcap in another thread (it will run forever) reactor.callInThread(run_pcap, self.pcapDataReceived) def dataReceived(self, data): print 'echo got: %s' % data def pcapDataReceived(self, sz): self.transport.write('pcap got: %uB\n' % sz) # starts the silly echo server on port 9999 def main(): factory = Factory() factory.protocol = Echo reactor.listenTCP(9999, factory) reactor.run() if __name__ == "__main__": main()