1# Future imports for Python 2.7, mandatory in 3.0 2from __future__ import division 3from __future__ import print_function 4from __future__ import unicode_literals 5 6import errno 7import logging 8import os 9import random 10import socket 11import subprocess 12import sys 13import time 14 15LOG_TIMEOUT = 60.0 16LOG_WAIT = 0.1 17 18def fail(msg): 19 logging.error('FAIL') 20 sys.exit(msg) 21 22def skip(msg): 23 logging.warning('SKIP: {}'.format(msg)) 24 sys.exit(77) 25 26def try_connecting_to_socksport(): 27 socks_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 e = socks_socket.connect_ex(('127.0.0.1', socks_port)) 29 if e: 30 tor_process.terminate() 31 fail('Cannot connect to SOCKSPort: error ' + os.strerror(e)) 32 socks_socket.close() 33 34def wait_for_log(s): 35 cutoff = time.time() + LOG_TIMEOUT 36 while time.time() < cutoff: 37 l = tor_process.stdout.readline() 38 l = l.decode('utf8', 'backslashreplace') 39 if s in l: 40 logging.info('Tor logged: "{}"'.format(l.strip())) 41 return 42 # readline() returns a blank string when there is no output 43 # avoid busy-waiting 44 if len(l) == 0: 45 logging.debug('Tor has not logged anything, waiting for "{}"'.format(s)) 46 time.sleep(LOG_WAIT) 47 else: 48 logging.info('Tor logged: "{}", waiting for "{}"'.format(l.strip(), s)) 49 fail('Could not find "{}" in logs after {} seconds'.format(s, LOG_TIMEOUT)) 50 51def pick_random_port(): 52 port = 0 53 random.seed() 54 55 for i in range(8): 56 port = random.randint(10000, 60000) 57 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 58 if s.connect_ex(('127.0.0.1', port)) == 0: 59 s.close() 60 else: 61 break 62 63 if port == 0: 64 fail('Could not find a random free port between 10000 and 60000') 65 66 return port 67 68logging.basicConfig(level=logging.DEBUG, 69 format='%(asctime)s.%(msecs)03d %(message)s', 70 datefmt='%Y-%m-%d %H:%M:%S') 71 72if sys.hexversion < 0x02070000: 73 fail("ERROR: unsupported Python version (should be >= 2.7)") 74 75if sys.hexversion > 0x03000000 and sys.hexversion < 0x03010000: 76 fail("ERROR: unsupported Python3 version (should be >= 3.1)") 77 78if 'TOR_SKIP_TEST_REBIND' in os.environ: 79 skip('$TOR_SKIP_TEST_REBIND is set') 80 81control_port = pick_random_port() 82socks_port = pick_random_port() 83 84assert control_port != 0 85assert socks_port != 0 86 87if len(sys.argv) < 3: 88 fail('Usage: %s <path-to-tor> <data-dir>' % sys.argv[0]) 89 90if not os.path.exists(sys.argv[1]): 91 fail('ERROR: cannot find tor at %s' % sys.argv[1]) 92if not os.path.exists(sys.argv[2]): 93 fail('ERROR: cannot find datadir at %s' % sys.argv[2]) 94 95tor_path = sys.argv[1] 96data_dir = sys.argv[2] 97 98empty_torrc_path = os.path.join(data_dir, 'empty_torrc') 99open(empty_torrc_path, 'w').close() 100empty_defaults_torrc_path = os.path.join(data_dir, 'empty_defaults_torrc') 101open(empty_defaults_torrc_path, 'w').close() 102 103tor_process = subprocess.Popen([tor_path, 104 '-DataDirectory', data_dir, 105 '-ControlPort', '127.0.0.1:{}'.format(control_port), 106 '-SOCKSPort', '127.0.0.1:{}'.format(socks_port), 107 '-Log', 'debug stdout', 108 '-LogTimeGranularity', '1', 109 '-FetchServerDescriptors', '0', 110 '-f', empty_torrc_path, 111 '--defaults-torrc', empty_defaults_torrc_path, 112 ], 113 stdout=subprocess.PIPE, 114 stderr=subprocess.PIPE) 115 116if tor_process == None: 117 fail('ERROR: running tor failed') 118 119wait_for_log('Opened Control listener') 120 121try_connecting_to_socksport() 122 123control_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 124if control_socket.connect_ex(('127.0.0.1', control_port)): 125 tor_process.terminate() 126 fail('Cannot connect to ControlPort') 127 128control_socket.sendall('AUTHENTICATE \r\n'.encode('ascii')) 129control_socket.sendall('SETCONF SOCKSPort=0.0.0.0:{}\r\n'.format(socks_port).encode('ascii')) 130wait_for_log('Opened Socks listener') 131 132try_connecting_to_socksport() 133 134control_socket.sendall('SETCONF SOCKSPort=127.0.0.1:{}\r\n'.format(socks_port).encode('ascii')) 135wait_for_log('Opened Socks listener') 136 137try_connecting_to_socksport() 138 139control_socket.sendall('SIGNAL HALT\r\n'.encode('ascii')) 140 141wait_for_log('exiting cleanly') 142logging.info('OK') 143 144try: 145 tor_process.terminate() 146except OSError as e: 147 if e.errno == errno.ESRCH: # errno 3: No such process 148 # assume tor has already exited due to SIGNAL HALT 149 logging.warn("Tor has already exited") 150 else: 151 raise 152