verify.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. import time
  2. import chutney
  3. def run_test(network):
  4. print("Verifying data transmission:")
  5. status = _verify_traffic(network)
  6. print("Transmission: %s" % ("Success" if status else "Failure"))
  7. if not status:
  8. # TODO: allow the debug flag to be passed as an argument to
  9. # src/test/test-network.sh and chutney
  10. print("Set 'debug_flag = True' in Traffic.py to diagnose.")
  11. return status
  12. def _verify_traffic(network):
  13. """Verify (parts of) the network by sending traffic through it
  14. and verify what is received."""
  15. LISTEN_ADDR = network._dfltEnv['ip']
  16. LISTEN_PORT = 4747 # FIXME: Do better! Note the default exit policy.
  17. # HSs must have a HiddenServiceDir with
  18. # "HiddenServicePort <HS_PORT> <CHUTNEY_LISTEN_ADDRESS>:<LISTEN_PORT>"
  19. HS_PORT = 5858
  20. # The amount of data to send between each source-sink pair,
  21. # each time the source connects.
  22. # We create a source-sink pair for each (bridge) client to an exit,
  23. # and a source-sink pair for a (bridge) client to each hidden service
  24. DATALEN = network._dfltEnv['data_bytes']
  25. # Print a dot each time a sink verifies this much data
  26. DOTDATALEN = 5 * 1024 * 1024 # Octets.
  27. TIMEOUT = 3 # Seconds.
  28. # Calculate the amount of random data we should use
  29. randomlen = _calculate_randomlen(DATALEN)
  30. reps = _calculate_reps(DATALEN, randomlen)
  31. connection_count = network._dfltEnv['connection_count']
  32. # sanity check
  33. if reps == 0:
  34. DATALEN = 0
  35. # Get the random data
  36. if randomlen > 0:
  37. # print a dot after every DOTDATALEN data is verified, rounding up
  38. dot_reps = _calculate_reps(DOTDATALEN, randomlen)
  39. # make sure we get at least one dot per transmission
  40. dot_reps = min(reps, dot_reps)
  41. with open('/dev/urandom', 'r') as randfp:
  42. tmpdata = randfp.read(randomlen)
  43. else:
  44. dot_reps = 0
  45. tmpdata = {}
  46. # now make the connections
  47. bind_to = (LISTEN_ADDR, LISTEN_PORT)
  48. tt = chutney.Traffic.TrafficTester(bind_to, tmpdata, TIMEOUT, reps,
  49. dot_reps)
  50. client_list = filter(lambda n:
  51. n._env['tag'] == 'c' or n._env['tag'] == 'bc',
  52. network._nodes)
  53. exit_list = filter(lambda n:
  54. ('exit' in n._env.keys()) and n._env['exit'] == 1,
  55. network._nodes)
  56. hs_list = filter(lambda n:
  57. n._env['tag'] == 'h',
  58. network._nodes)
  59. if len(client_list) == 0:
  60. print(" Unable to verify network: no client nodes available")
  61. return False
  62. if len(exit_list) == 0 and len(hs_list) == 0:
  63. print(" Unable to verify network: no exit/hs nodes available")
  64. print(" Exit nodes must be declared 'relay=1, exit=1'")
  65. print(" HS nodes must be declared 'tag=\"hs\"'")
  66. return False
  67. print("Connecting:")
  68. # the number of tor nodes in paths which will send DATALEN data
  69. # if a node is used in two paths, we count it twice
  70. # this is a lower bound, as cannabilised circuits are one node longer
  71. total_path_node_count = 0
  72. total_path_node_count += _configure_exits(tt, bind_to, tmpdata, reps,
  73. client_list, exit_list,
  74. LISTEN_ADDR, LISTEN_PORT,
  75. connection_count)
  76. total_path_node_count += _configure_hs(tt, tmpdata, reps, client_list,
  77. hs_list, HS_PORT, LISTEN_ADDR,
  78. LISTEN_PORT, connection_count,
  79. network._dfltEnv['hs_multi_client'])
  80. print("Transmitting Data:")
  81. start_time = time.clock()
  82. status = tt.run()
  83. end_time = time.clock()
  84. # if we fail, don't report the bandwidth
  85. if not status:
  86. return status
  87. # otherwise, report bandwidth used, if sufficient data was transmitted
  88. _report_bandwidth(DATALEN, total_path_node_count, start_time, end_time)
  89. return status
  90. # In order to performance test a tor network, we need to transmit
  91. # several hundred megabytes of data or more. Passing around this
  92. # much data in Python has its own performance impacts, so we provide
  93. # a smaller amount of random data instead, and repeat it to DATALEN
  94. def _calculate_randomlen(datalen):
  95. MAX_RANDOMLEN = 128 * 1024 # Octets.
  96. if datalen > MAX_RANDOMLEN:
  97. return MAX_RANDOMLEN
  98. else:
  99. return datalen
  100. def _calculate_reps(datalen, replen):
  101. # sanity checks
  102. if datalen == 0 or replen == 0:
  103. return 0
  104. # effectively rounds datalen up to the nearest replen
  105. if replen < datalen:
  106. return (datalen + replen - 1) / replen
  107. else:
  108. return 1
  109. # if there are any exits, each client / bridge client transmits
  110. # via 4 nodes (including the client) to an arbitrary exit
  111. # Each client binds directly to <CHUTNEY_LISTEN_ADDRESS>:LISTEN_PORT
  112. # via an Exit relay
  113. def _configure_exits(tt, bind_to, tmpdata, reps, client_list, exit_list,
  114. LISTEN_ADDR, LISTEN_PORT, connection_count):
  115. CLIENT_EXIT_PATH_NODES = 4
  116. exit_path_node_count = 0
  117. if len(exit_list) > 0:
  118. exit_path_node_count += (len(client_list) *
  119. CLIENT_EXIT_PATH_NODES *
  120. connection_count)
  121. for op in client_list:
  122. print(" Exit to %s:%d via client %s:%s"
  123. % (LISTEN_ADDR, LISTEN_PORT,
  124. 'localhost', op._env['socksport']))
  125. for _ in range(connection_count):
  126. proxy = ('localhost', int(op._env['socksport']))
  127. tt.add(chutney.Traffic.Source(tt, bind_to, tmpdata, proxy,
  128. reps))
  129. return exit_path_node_count
  130. # The HS redirects .onion connections made to hs_hostname:HS_PORT
  131. # to the Traffic Tester's CHUTNEY_LISTEN_ADDRESS:LISTEN_PORT
  132. # an arbitrary client / bridge client transmits via 8 nodes
  133. # (including the client and hs) to each hidden service
  134. # Instead of binding directly to LISTEN_PORT via an Exit relay,
  135. # we bind to hs_hostname:HS_PORT via a hidden service connection
  136. def _configure_hs(tt, tmpdata, reps, client_list, hs_list, HS_PORT,
  137. LISTEN_ADDR, LISTEN_PORT, connection_count, hs_multi_client):
  138. CLIENT_HS_PATH_NODES = 8
  139. hs_path_node_count = (len(hs_list) * CLIENT_HS_PATH_NODES *
  140. connection_count)
  141. # Each client in hs_client_list connects to each hs
  142. if hs_multi_client:
  143. hs_client_list = client_list
  144. hs_path_node_count *= len(client_list)
  145. else:
  146. # only use the first client in the list
  147. hs_client_list = client_list[:1]
  148. # Setup the connections from each client in hs_client_list to each hs
  149. for hs in hs_list:
  150. hs_bind_to = (hs._env['hs_hostname'], HS_PORT)
  151. for client in hs_client_list:
  152. print(" HS to %s:%d (%s:%d) via client %s:%s"
  153. % (hs._env['hs_hostname'], HS_PORT,
  154. LISTEN_ADDR, LISTEN_PORT,
  155. 'localhost', client._env['socksport']))
  156. for _ in range(connection_count):
  157. proxy = ('localhost', int(client._env['socksport']))
  158. tt.add(chutney.Traffic.Source(tt, hs_bind_to, tmpdata,
  159. proxy, reps))
  160. return hs_path_node_count
  161. # calculate the single stream bandwidth and overall tor bandwidth
  162. # the single stream bandwidth is the bandwidth of the
  163. # slowest stream of all the simultaneously transmitted streams
  164. # the overall bandwidth estimates the simultaneous bandwidth between
  165. # all tor nodes over all simultaneous streams, assuming:
  166. # * minimum path lengths (no cannibalized circuits)
  167. # * unlimited network bandwidth (that is, localhost)
  168. # * tor performance is CPU-limited
  169. # This be used to estimate the bandwidth capacity of a CPU-bound
  170. # tor relay running on this machine
  171. def _report_bandwidth(data_length, total_path_node_count, start_time,
  172. end_time):
  173. # otherwise, if we sent at least 5 MB cumulative total, and
  174. # it took us at least a second to send, report bandwidth
  175. MIN_BWDATA = 5 * 1024 * 1024 # Octets.
  176. MIN_ELAPSED_TIME = 1.0 # Seconds.
  177. cumulative_data_sent = total_path_node_count * data_length
  178. elapsed_time = end_time - start_time
  179. if (cumulative_data_sent >= MIN_BWDATA and
  180. elapsed_time >= MIN_ELAPSED_TIME):
  181. # Report megabytes per second
  182. BWDIVISOR = 1024*1024
  183. single_stream_bandwidth = (data_length / elapsed_time / BWDIVISOR)
  184. overall_bandwidth = (cumulative_data_sent / elapsed_time /
  185. BWDIVISOR)
  186. print("Single Stream Bandwidth: %.2f MBytes/s"
  187. % single_stream_bandwidth)
  188. print("Overall tor Bandwidth: %.2f MBytes/s"
  189. % overall_bandwidth)