[tor-commits] [obfsproxy/master] Flesh tester.py.in out to subsume the existing non-automated integration tests. Note: all tests presently fail due to bugs introduced somewhere on this branch.

nickm at torproject.org nickm at torproject.org
Fri Sep 9 17:08:57 UTC 2011


commit a99ca9dd19d3fc8e290b04ad3af9251a43caa4d1
Author: Zack Weinberg <zackw at panix.com>
Date:   Tue Jul 26 15:44:08 2011 -0700

    Flesh tester.py.in out to subsume the existing non-automated integration tests.  Note: all tests presently fail due to bugs introduced somewhere on this branch.
---
 src/test/integration_test/alpha       |   38 ----
 src/test/integration_test/int_test.sh |   76 -------
 src/test/test_socks_unsupported.py    |   18 --
 src/test/tester.py.in                 |  354 ++++++++++++++++++++++++++++++++-
 4 files changed, 348 insertions(+), 138 deletions(-)

diff --git a/src/test/integration_test/alpha b/src/test/integration_test/alpha
deleted file mode 100644
index b6d68d6..0000000
--- a/src/test/integration_test/alpha
+++ /dev/null
@@ -1,38 +0,0 @@
-THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
-THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
-THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
-THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
-
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-"Can entropy ever be reversed?"
-"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
-
-    In obfuscatory age geeky warfare did I wage
-      For hiding bits from nasty censors' sight
-    I was hacker to my set in that dim dark age of net
-      And I hacked from noon till three or four at night
-
-    Then a rival from Helsinki said my protocol was dinky
-      So I flamed him with a condescending laugh,
-    Saying his designs for stego might as well be made of lego
-      And that my bikeshed was prettier by half.
-
-    But Claude Shannon saw my shame. From his noiseless channel came
-       A message sent with not a wasted byte
-    "There are nine and sixty ways to disguise communiques
-       And RATHER MORE THAN ONE OF THEM IS RIGHT"
-
-		    (apologies to Rudyard Kipling.)
diff --git a/src/test/integration_test/int_test.sh b/src/test/integration_test/int_test.sh
deleted file mode 100644
index 9aa37ac..0000000
--- a/src/test/integration_test/int_test.sh
+++ /dev/null
@@ -1,76 +0,0 @@
-#!/bin/bash
-
-# replace this with your path to obfsproxy.
-OBFSPROXY="../../../obfsproxy"
-# replace this with your path to ncat.
-NCAT=ncat
-
-ENTRY_PORT=4999
-SERVER_PORT=5000
-NCAT_PORT=5001
-
-DIR=inttemp_temp
-FILE1=$DIR/test1
-FILE2=$DIR/test2
-
-mkdir -p $DIR ; :>$FILE1
-
-# TEST 1
-# We open a server and a client and transfer a file. Then we check if the output of the
-# server is the same as the file we sent.
-
-$NCAT -k -l -o $FILE1 -p $NCAT_PORT > /dev/null &
-ncat1_pid=$!
-
-
-$OBFSPROXY --log-min-severity=warn obfs2 --dest=127.0.0.1:$NCAT_PORT server 127.0.0.1:$SERVER_PORT \
-    + obfs2 --dest=127.0.0.1:$SERVER_PORT client 127.0.0.1:$ENTRY_PORT &
-obfsproxy_pid=$!
-sleep 1
-
-
-$NCAT localhost $ENTRY_PORT < alpha &
-ncat2_pid=$!
-sleep 2
-
-if cmp -s alpha $FILE1
-then echo "GREAT SUCCESS 1!" ; rm $FILE1
-else echo "GREAT FAIL 1!"
-fi
-
-kill -9 $ncat1_pid
-kill -9 $obfsproxy_pid
-kill -9 $ncat2_pid
-
-sleep 2
-
-# TEST 2
-# We open an obfsproxy SOCKS server on the dummy protocol and an ncat listening.
-# Then we configure another ncat to use SOCKS4 and transfer a file to the other ncat.
-# Finally, we check if the file was sent correctly.
-
-:>$FILE2
-
-$NCAT -k -l -o $FILE2 -p $NCAT_PORT > /dev/null &
-ncat1_pid=$!
-
-$OBFSPROXY --log-min-severity=warn dummy socks 127.0.0.1:$SERVER_PORT &
-obfsproxy_pid=$!
-sleep 1
-
-$NCAT --proxy-type socks4 --proxy 127.0.0.1:$SERVER_PORT \
-    127.0.0.1 $NCAT_PORT < alpha &
-ncat2_pid=$!
-sleep 2
-
-if cmp -s alpha $FILE2
-then echo "GREAT SUCCESS 2!" ; rm $FILE2
-else echo "GREAT FAIL 2!"
-fi
-
-kill -9 $ncat1_pid
-kill -9 $obfsproxy_pid
-kill -9 $ncat2_pid
-
-rmdir $DIR
-
diff --git a/src/test/test_socks_unsupported.py b/src/test/test_socks_unsupported.py
deleted file mode 100644
index d1afd02..0000000
--- a/src/test/test_socks_unsupported.py
+++ /dev/null
@@ -1,18 +0,0 @@
-import socket,struct
-
-negot = struct.pack('BBB', 5, 1, 0)
-request = struct.pack('BBBBBBB', 5, 2, 0, 1, 1, 1, 1)
-
-PORT = 4500
-
-s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-s.connect(("127.0.0.1",PORT))
-s.send(negot)
-s.recv(1024)
-s.send(request)
-data = s.recv(1024)
-if (struct.unpack('BBBBih', data)[1] == 7):
-    print "Works."
-else:
-    print "Fail!"
-
diff --git a/src/test/tester.py.in b/src/test/tester.py.in
index d330e69..b0602ed 100644
--- a/src/test/tester.py.in
+++ b/src/test/tester.py.in
@@ -5,14 +5,356 @@
 # The obfsproxy binary is assumed to exist in the current working
 # directory, and you need to have Python 2.6 or better (but not 3).
 # You need to be able to make connections to arbitrary high-numbered
-# TCP ports on the loopback interface (there is, however, an effort to
-# figure out which ones are already in use) and all IPv4 addresses in
-# 127.0.0/24 are assumed to be bound to the loopback interface.
+# TCP ports on the loopback interface.
 
+import errno
+import multiprocessing
+import Queue
+import signal
 import socket
 import struct
 import subprocess
-import sys
+import time
+import unittest
 
-print "All tests successful."
-sys.exit(0)
+# Helper: Repeatedly try to connect to the specified server socket
+# until either it succeeds or one full second has elapsed.  (Surely
+# there is a better way to do this?)
+
+def connect_with_retry(addr):
+    retry = 0
+    while True:
+        try:
+            return socket.create_connection(addr)
+        except socket.error, e:
+            if e.errno != errno.ECONNREFUSED: raise
+            if retry == 20: raise
+            retry += 1
+            time.sleep(0.05)
+
+# Helper: In a separate process (to avoid deadlock), listen on a
+# specified socket.  The first time something connects to that socket,
+# read all available data, stick it in a string, and post the string
+# to the output queue.  Then close both sockets and exit.
+
+class ReadWorker(object):
+    @staticmethod
+    def work(address, oq):
+        listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        listener.bind(address)
+        listener.listen(1)
+        (conn, remote) = listener.accept()
+        data = ""
+        try:
+            while True:
+                chunk = conn.recv(4096)
+                if chunk == "": break
+                data += chunk
+        except Exception, e:
+            data += "|RECV ERROR: " + e
+        conn.close()
+        listener.close()
+        oq.put(data)
+
+    def __init__(self, address):
+        self.oq = multiprocessing.Queue()
+        self.worker = multiprocessing.Process(target=self.work,
+                                              args=(address, self.oq))
+        self.worker.start()
+
+    def get(self):
+        rv = self.oq.get(timeout=1)
+        self.worker.join()
+        return rv
+
+    def stop(self):
+        if self.worker.is_alive(): self.worker.terminate()
+
+# Right now this is a direct translation of the former int_test.sh
+# (except that I have fleshed out the SOCKS test a bit).
+# It will be made more general and parametric Real Soon.
+
+ENTRY_PORT  = 4999
+SERVER_PORT = 5000
+EXIT_PORT   = 5001
+
+#
+# Test base classes.  They do _not_ inherit from unittest.TestCase
+# so that they are not scanned directly for test functions (some of
+# them do provide test functions, but not in a usable state without
+# further code from subclasses).
+#
+
+class DirectTest(object):
+    def setUp(self):
+        self.output_reader = ReadWorker(("127.0.0.1", EXIT_PORT))
+        self.obfs = subprocess.Popen(
+            self.obfs_args,
+            stdin=open("/dev/null", "r"),
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+
+        self.input_chan = connect_with_retry(("127.0.0.1", ENTRY_PORT))
+        self.input_chan.settimeout(1.0)
+
+    def tearDown(self):
+        if self.obfs.returncode is None:
+            self.obfs.terminate()
+        self.output_reader.stop()
+
+    def checkSubprocesses(self):
+        if self.obfs.poll() is None:
+            self.obfs.send_signal(signal.SIGINT)
+
+        (out, err) = self.obfs.communicate()
+
+        if (out != "" or err != "" or self.obfs.returncode != 0):
+            self.fail("obfsproxy process failure:\n"
+                      "\treturn code: %d\n"
+                      "\tstdout: %s\n"
+                      "\tstderr: %s\n"
+                      % (self.obfs.returncode, out, err))
+
+    def test_direct_transfer(self):
+        # Open a server and a simple client (in the same process) and
+        # transfer a file.  Then check whether the output is the same
+        # as the input.
+        self.input_chan.sendall(TEST_FILE)
+        self.input_chan.shutdown(socket.SHUT_WR)
+        try:
+            output = self.output_reader.get()
+        except Queue.Empty:
+            output = ""
+
+        self.checkSubprocesses()
+        self.assertEqual(output, TEST_FILE)
+
+# Same as above, but we use a socks client instead of a simple client,
+# and the server's a separate process.
+
+class SocksTest(object):
+    def setUp(self):
+        self.output_reader = ReadWorker(("127.0.0.1", EXIT_PORT))
+        self.obfs_server = subprocess.Popen(
+            self.server_args,
+            stdin=open("/dev/null", "r"),
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+        self.obfs_client = subprocess.Popen(
+            self.client_args,
+            stdin=open("/dev/null", "r"),
+            stdout=subprocess.PIPE,
+            stderr=subprocess.PIPE)
+
+        self.input_chan = connect_with_retry(("127.0.0.1", ENTRY_PORT))
+        self.input_chan.settimeout(1.0)
+
+    def tearDown(self):
+        if self.obfs_server.returncode is None:
+            self.obfs_server.terminate()
+        if self.obfs_client.returncode is None:
+            self.obfs_client.terminate()
+        self.output_reader.stop()
+
+    def checkSubprocesses(self):
+        if self.obfs_server.poll() is None:
+            self.obfs_server.send_signal(signal.SIGINT)
+        if self.obfs_client.poll() is None:
+            self.obfs_client.send_signal(signal.SIGINT)
+
+        (sout, serr) = self.obfs_server.communicate()
+        (cout, cerr) = self.obfs_client.communicate()
+
+        if (sout != "" or serr != "" or cout != "" or cerr != ""
+            or self.obfs_server.returncode != 0
+            or self.obfs_client.returncode != 0):
+            self.fail("obfsproxy process failures:\n"
+                      "\tclient return code: %d\n"
+                      "\tserver return code: %d\n"
+                      "\tclient stdout: %s\n"
+                      "\tclient stderr: %s\n"
+                      "\tserver stdout: %s\n"
+                      "\tserver stderr: %s\n"
+                      % (self.obfs_client.returncode,
+                         self.obfs_server.returncode,
+                         cout, cerr, sout, serr))
+
+    # 'sequence' is a sequence of SOCKS[45] protocol messages
+    # which we will send or receive.  Sends alternate with
+    # receives.  Each entry may be a string, which is sent or
+    # received verbatim; a pair of a sequence of data items and a
+    # struct pack code, which is packed and then sent or received;
+    # or the constant False, which means the server is expected to
+    # drop the connection at that point.  If we come to the end of
+    # the SOCKS sequence without the server having dropped the
+    # connection, we transmit the test file and expect to get it
+    # back from the far end.
+    def socksTest(self, sequence):
+        sending = True
+        good = True
+        for msg in sequence:
+            if msg is False:
+                self.input_chan.shutdown(socket.SHUT_WR)
+                # Expect either a clean closedown or a connection reset
+                # at this point.
+                got = ""
+                try:
+                    got = self.input_chan.recv(4096)
+                except socket.error, e:
+                    if e.errno != errno.ECONNRESET: raise
+                self.assertEqual(got, "")
+                good = False
+                break
+            elif isinstance(msg, str):
+                exp = msg
+            elif isinstance(msg, tuple):
+                exp = struct.pack(msg[1], *msg[0])
+            else:
+                raise TypeError("incomprehensible msg: " + repr(msg))
+            if sending:
+                self.input_chan.sendall(exp)
+            else:
+                got = ""
+                try:
+                    got = self.input_chan.recv(4096)
+                except socket.error, e:
+                    if e.errno != errno.ECONNRESET: raise
+                self.assertEqual(got, exp)
+        if good:
+            self.input_chan.sendall(TEST_FILE)
+            self.input_chan.shutdown(socket.SHUT_WR)
+            try:
+                output = self.output_reader.get()
+            except Queue.Empty:
+                output = ""
+            self.assertEqual(output, TEST_FILE)
+
+        self.input_chan.close()
+        self.checkSubprocesses()
+
+    def test_illformed(self):
+        # ill-formed socks message - server should drop connection
+        self.socksTest([ "GET / HTTP/1.1\r\nHost: 127.0.0.1\r\n"
+                         "Connection: close\r\n\r\n",
+                         False ])
+
+    def test_socks4_unsupported_methods(self):
+        # SOCKS4 bind request - should fail, presently just drops connection
+        self.socksTest([ ( (4, 2, SERVER_PORT, 127, 0, 0, 1, 0), "!BBH5B" ),
+                         False ])
+
+    def test_socks4_connect(self):
+        # SOCKS4 connection request - should succeed
+        self.socksTest([ ( (4, 1, SERVER_PORT, 127, 0, 0, 1, 0), "!BBH5B" ),
+                         ( (0, 90, SERVER_PORT, 127, 0, 0, 1), "!BBH4B" ) ])
+
+    def test_socks5_bad_handshakes(self):
+        self.socksTest([ "\x05", False ])
+        self.socksTest([ "\x05\x00", False ])
+        self.socksTest([ "\x05\x01\x01", "\x05\xFF", False ])
+        self.socksTest([ "\x05\x01\x080", "\x05\xFF", False ])
+        self.socksTest([ "\x05\x02\x01\x02", "\x05\xFF", False ])
+
+    def test_socks5_bad_requests(self):
+        self.socksTest([ "\x05\x01\x00", "\x05\x00", "\x05\x00",
+                         "\x05\x07\x00", False ])
+        self.socksTest([ "\x05\x02\x00\x01", "\x05\x00", "\x05\x00",
+                         "\x05\x07\x00", False ])
+
+    def test_socks5_unsupported_methods(self):
+        self.socksTest([ "\x05\x01\x00", "\x05\x00",
+                         ( (5, 2, 0, 1, 127, 0, 0, 1, SERVER_PORT), "!8BH" ),
+                         "\x05\x07\x00", False ])
+        self.socksTest([ "\x05\x01\x00", "\x05\x00",
+                         ( (5, 3, 0, 1, 127, 0, 0, 1, SERVER_PORT), "!8BH" ),
+                         "\x05\x07\x00", False ])
+
+    def test_socks5_connect(self):
+        self.socksTest([ "\x05\x01\x00", "\x05\x00",
+                         ( (5, 1, 0, 1, 127, 0, 0, 1, SERVER_PORT), "!8BH" ),
+                         ( (5, 0, 0, 1, 127, 0, 0, 1, SERVER_PORT), "!8BH" ) ])
+
+#
+# Concrete test classes specialize the above base classes for each protocol.
+#
+
+class DirectObfs2(DirectTest, unittest.TestCase):
+    obfs_args = ("./obfsproxy", "--log-min-severity=warn",
+                 "obfs2",
+                 "--dest=127.0.0.1:%d" % EXIT_PORT,
+                 "server", "127.0.0.1:%d" % SERVER_PORT,
+                 "+", "obfs2",
+                 "--dest=127.0.0.1:%d" % SERVER_PORT,
+                 "client", "127.0.0.1:%d" % ENTRY_PORT)
+
+class DirectDummy(DirectTest, unittest.TestCase):
+    obfs_args = ("./obfsproxy", "--log-min-severity=warn",
+                 "dummy", "server",
+                 "127.0.0.1:%d" % SERVER_PORT,
+                 "127.0.0.1:%d" % EXIT_PORT,
+                 "+", "dummy", "client",
+                 "127.0.0.1:%d" % ENTRY_PORT,
+                 "127.0.0.1:%d" % SERVER_PORT)
+
+class SocksObfs2(SocksTest, unittest.TestCase):
+    server_args = ("./obfsproxy", "--log-min-severity=warn",
+                   "obfs2",
+                   "--dest=127.0.0.1:%d" % EXIT_PORT,
+                   "server", "127.0.0.1:%d" % SERVER_PORT)
+    client_args = ("./obfsproxy", "--log-min-severity=warn",
+                   "obfs2",
+                   "socks", "127.0.0.1:%d" % ENTRY_PORT)
+
+class SocksDummy(SocksTest, unittest.TestCase):
+    server_args = ("./obfsproxy", "--log-min-severity=warn",
+                   "dummy", "server",
+                   "127.0.0.1:%d" % SERVER_PORT,
+                   "127.0.0.1:%d" % EXIT_PORT)
+    client_args = ("./obfsproxy", "--log-min-severity=warn",
+                   "dummy", "socks",
+                   "127.0.0.1:%d" % ENTRY_PORT)
+
+TEST_FILE = """\
+THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
+THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
+THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
+THIS IS A TEST FILE. IT'S USED BY THE INTEGRATION TESTS.
+
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+"Can entropy ever be reversed?"
+"THERE IS AS YET INSUFFICIENT DATA FOR A MEANINGFUL ANSWER."
+
+    In obfuscatory age geeky warfare did I wage
+      For hiding bits from nasty censors' sight
+    I was hacker to my set in that dim dark age of net
+      And I hacked from noon till three or four at night
+
+    Then a rival from Helsinki said my protocol was dinky
+      So I flamed him with a condescending laugh,
+    Saying his designs for stego might as well be made of lego
+      And that my bikeshed was prettier by half.
+
+    But Claude Shannon saw my shame. From his noiseless channel came
+       A message sent with not a wasted byte
+    "There are nine and sixty ways to disguise communiques
+       And RATHER MORE THAN ONE OF THEM IS RIGHT"
+
+		    (apologies to Rudyard Kipling.)
+"""
+
+if __name__ == '__main__':
+    unittest.main()





More information about the tor-commits mailing list