Merge into master from pull request #68:
built-in pcap captures (https://github.com/floodlight/oftest/pull/68)
diff --git a/oft b/oft
index b79d46c..a4aa5b4 100755
--- a/oft
+++ b/oft
@@ -232,6 +232,19 @@
 
     oftest.open_logfile('main')
 
+def pcap_setup(config):
+    """
+    Set up dataplane packet capturing based on config
+    """
+
+    if config["log_dir"] == None:
+        filename = os.path.splitext(config["log_file"])[0] + '.pcap'
+        oftest.dataplane_instance.start_pcap(filename)
+    else:
+        # start_pcap is called per-test in base_tests
+        pass
+
+
 def load_test_modules(config):
     """
     Load tests from the test_dir directory.
@@ -498,6 +511,7 @@
 if __name__ == "__main__":
     # Set up the dataplane
     oftest.dataplane_instance = oftest.dataplane.DataPlane(config)
+    pcap_setup(config)
     for of_port, ifname in config["port_map"].items():
         oftest.dataplane_instance.port_add(ifname, of_port)
 
diff --git a/src/python/oftest/base_tests.py b/src/python/oftest/base_tests.py
index 3b59bc1..a2b25ac 100644
--- a/src/python/oftest/base_tests.py
+++ b/src/python/oftest/base_tests.py
@@ -7,6 +7,7 @@
 
 import logging
 import unittest
+import os
 
 import oftest
 from oftest import config
@@ -103,6 +104,9 @@
         SimpleProtocol.setUp(self)
         self.dataplane = oftest.dataplane_instance
         self.dataplane.flush()
+        if config["log_dir"] != None:
+            filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
+            self.dataplane.start_pcap(filename)
 
     def inheritSetup(self, parent):
         """
@@ -114,6 +118,8 @@
         self.dataplane = parent.dataplane
 
     def tearDown(self):
+        if config["log_dir"] != None:
+            self.dataplane.stop_pcap()
         SimpleProtocol.tearDown(self)
 
 class DataPlaneOnly(BaseTest):
@@ -125,6 +131,11 @@
         BaseTest.setUp(self)
         self.dataplane = oftest.dataplane_instance
         self.dataplane.flush()
+        if config["log_dir"] != None:
+            filename = os.path.join(config["log_dir"], str(self)) + ".pcap"
+            self.dataplane.start_pcap(filename)
 
     def tearDown(self):
+        if config["log_dir"] != None:
+            self.dataplane.stop_pcap()
         BaseTest.tearDown(self)
diff --git a/src/python/oftest/dataplane.py b/src/python/oftest/dataplane.py
index f70de6c..12dcfce 100644
--- a/src/python/oftest/dataplane.py
+++ b/src/python/oftest/dataplane.py
@@ -25,6 +25,7 @@
 from threading import Condition
 import ofutils
 import netutils
+from pcap_writer import PcapWriter
 
 have_pypcap = False
 try:
@@ -165,6 +166,7 @@
         self.killed = False
 
         self.logger = logging.getLogger("dataplane")
+        self.pcap_writer = None
 
         if config is None:
             self.config = {}
@@ -215,6 +217,8 @@
                         port_number = port._port_number
                         self.logger.debug("Pkt len %d in on port %d",
                                           len(pkt), port_number)
+                        if self.pcap_writer:
+                            self.pcap_writer.write(pkt, timestamp, port_number)
                         queue = self.packet_queues[port_number]
                         if len(queue) >= self.MAX_QUEUE_LEN:
                             # Queue full, throw away oldest
@@ -246,6 +250,8 @@
         """
         self.logger.debug("Sending %d bytes to port %d" %
                           (len(packet), port_number))
+        if self.pcap_writer:
+            self.pcap_writer.write(packet, time.time(), port_number)
         bytes = self.ports[port_number].send(packet)
         if bytes != len(packet):
             self.logger.error("Unhandled send error, length mismatch %d != %d" %
@@ -353,3 +359,12 @@
         """
         for port_number in self.packet_queues.keys():
             self.packet_queues[port_number] = []
+
+    def start_pcap(self, filename):
+        assert(self.pcap_writer == None)
+        self.pcap_writer = PcapWriter(filename)
+
+    def stop_pcap(self):
+        if self.pcap_writer:
+            self.pcap_writer.close()
+            self.pcap_writer = None
diff --git a/src/python/oftest/pcap_writer.py b/src/python/oftest/pcap_writer.py
new file mode 100644
index 0000000..6dce530
--- /dev/null
+++ b/src/python/oftest/pcap_writer.py
@@ -0,0 +1,61 @@
+"""
+Pcap file writer
+"""
+
+import struct
+
+PcapHeader = struct.Struct("<LHHLLLL")
+PcapPktHeader = struct.Struct("<LLLL")
+PPIPktHeader = struct.Struct("<BBHL")
+PPIAggregateField = struct.Struct("<HHL")
+
+class PcapWriter(object):
+    def __init__(self, filename):
+        """
+        Open a pcap file
+        """
+        self.stream = file(filename, 'w')
+
+        self.stream.write(PcapHeader.pack(
+            0xa1b2c3d4, # magic
+            2, # major
+            4, # minor
+            0, # timezone offset
+            0, # timezone accuracy
+            65535, # snapshot length
+            192 # PPI linktype
+        ))
+
+    def write(self, data, timestamp, port):
+        """
+        Write a packet to a pcap file
+
+        'data' should be a string containing the packet data.
+        'timestamp' should be a float.
+        'port' should be an integer port number.
+        """
+        ppi_len = PPIPktHeader.size + PPIAggregateField.size
+        self.stream.write(PcapPktHeader.pack(
+            int(timestamp), # timestamp seconds
+            int((timestamp - int(timestamp)) * 10**6), # timestamp microseconds
+            len(data) + ppi_len, # truncated length
+            len(data) + ppi_len # un-truncated length
+        ))
+        self.stream.write(PPIPktHeader.pack(
+            0, # version
+            0, # flags
+            ppi_len, # length
+            1, # ethernet dlt
+        ))
+        self.stream.write(PPIAggregateField.pack(8, PPIAggregateField.size - 4, port))
+        self.stream.write(data)
+
+    def close(self):
+        self.stream.close()
+
+if __name__ == "__main__":
+    import time
+    print("Writing test pcap to test.pcap")
+    pcap_writer = PcapWriter("test.pcap")
+    pcap_writer.write("\x00\x01\x02\x03\x04\x05\x00\x0a\x0b\x0c\x0d\x0e\x08\x00", time.time(), 42)
+    pcap_writer.close()