Tor:TController is an extension of the Tor::Controller class, providing more methods. It inherits all methods from Tor::Controller and can be used to interact with Tor.
TController examples
require 'tor_extend' Tor::TController.new(:host=>@getbridge_config[:torcontrolhost], :port=>@getbridge_config[:torcontrolport]) Tor::TController.new(:host=>"127.0.0.1",:port=>9051) Tor::TController.connect Tor::TController.authenticate("\"tor_control_password\"") Tor::TController.sr(:signal,"HUP") Tor::TController.sr(:signal,"newnym") Tor::TController.getinfo("ns/all") Tor::TController.net_status Tor::TController.closecircuit(15) Tor::TController.closeallcircuits Tor::TController.signal("reload" Tor::TController.extendcir(0,['or1','or2','or3']) Tor::TController.setconf("__DisablePredictedCircuits",1) Tor::TController.setconf("__LeaveStreamsUnattached",1) Tor::TController.getconf("ORPort") Tor::TController.getconf("__LeaveStreamsUnattached") Tor::TController.get_bridges( {:type=>'http',:port=>9050,:addr=>'127.0.0.1'} )
For more details, visit [github.com/bendiken/tor-ruby#readme], [gitweb.torproject.org/torspec.git/tree]
@param [Hash{Symbol => Object}] options @option options [String, to_s] :host (“127.0.0.1”) @option options [Integer, to_i] :port (9051) @option options [String, to_s] :cookie (nil) @option options [Integer, to_i] :version (PROTOCOL_VERSION)
# File lib/tcontroller.rb, line 42 def initialize(options = {}, &block) @options = options.dup @host = (@options.delete(:host) || '127.0.0.1').to_s @port = (@options.delete(:port) || 9051).to_i @version = (@options.delete(:version) || PROTOCOL_VERSION).to_i @passwd = (@options.delete(:password) || '').to_s connect authenticate "\"#{@passwd}\"" if ! @passwd != "" if block_given? block.call(self) quit end end
This attaches streams to circuits. No documentation yet.
# File lib/tcontroller.rb, line 266 def attach_stream(stream_no, cir_num ,*hop_count) # puts "attachstream #{cir_num}\n" if hop_count.empty? or hop_count[0] == 0 sr(:attachstream,"#{stream_no} #{cir_num}") else sr(:attachstream,"#{stream_no} #{cir_num} HOP=#{hop_count[0]}") end end
Returns all the brdiges in the database as an array.
#bridges => [{:ipaddr, :port, :lat, :lng},...]
# File lib/tcontroller.rb, line 248 def bridges Bridges.all end
This returns the circuit-status in an array, or an empty array on failure
Get the circuit-status from Tor.
Tor::TController.cir_status ["46 BUILT ORa,ORb,ORc PURPOSE=GENERAL"]
# File lib/tcontroller.rb, line 63 def cir_status cirstatus=getinfo("circuit-status") end
This attempts to close all open circuits.
Closing all circuits
Tor::TController.closeallcircuits
# File lib/tcontroller.rb, line 81 def closeallcircuits x=cir_status if !x.empty? x.each{|eachcircuit| circnum = eachcircuit.match(/^\d+/) closecircuit(circnum) } end end
This attempts to close a single circuit.
Closing circuits
Tor::TController.closecircuit(15)
# File lib/tcontroller.rb, line 72 def closecircuit (circnum) errorstate,ans=sr(:closecircuit," #{circnum}") end
Returns the an array of of directory servers from the consensus.
#ds => [w.x.y.z:port, ...]
# File lib/tcontroller.rb, line 95 def ds reply=[] s = getinfo("ns/all") case version when /0.2.[01]/ z=2 when/0.2.2/ z = 4 # To accommodate r, s , w and p] end s.collect!{|eachs| eachs.start_with?("r ") ? eachs : nil } s.delete nil s.each{|eachs| dsport=eachs.split[8].to_i rip=eachs.split[6] reply << "#{rip}:#{dsport}" if dsport!= 0 } reply end
This creates/extends a circuit over multiple nodes. A new circuit is created if cir_num == 0.
Creating a new circuit
create_circ = Tor::TController.extendcir(0,['or1','or2','or3'])
# File lib/tcontroller.rb, line 120 def extendcir(circnum,or_list) argor=" #{circnum} #{or_list.join(',')}" extendcir=send_command(:extendcircuit,argor) circuit_id=nil readterm=true while readterm do case msg=read_reply when /^\d+ CIRC \d+ BUILT/,/250 / #/^\d+ CIRC \d+ BUILT/ circuit_id=msg.scan(/\d+\Z/)[0] readterm=false when /^5\d\d / readterm=false when /^\d+ / puts msg #like extended launched else puts msg,"\n" end end #sr(:signal,"newnym") circuit_id end
This builds a circuit to each member of the array argument, skipping any node that fails to connect. It returns the circuit number, and the longest successful circuit that was successfully built. There is a delay of 2.0 seconds after each extension, but this can be altered by defining Tor::EXTEND_DELAY constant.
Create a circuit as long as possible using 10 elements in an array
testarray = [A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z] Tor::TController.extendcir_slowly(testarray)
# File lib/tcontroller.rb, line 149 def extendcir_slowly(orarray) ordelay = defined?(EXTEND_DELAY) ? EXTEND_DELAY : Constants::EXTEND_DELAY cirnum = 0 circuit = [] orarray.each{|eachor| cirnum=extendcir(cirnum,[eachor]) sleep(ordelay) # wait for a few seconds, and check if the circuit is was successful p=cir_status.detect{|i| i =~ (/^#{cirnum} / )} case p when /FAILED/,nil puts "cirnum=nil" if eachor != circuit.last cirnum = extendcir(0,circuit) sleep(circuit.count) elsif circuit.empty? return nil # failed to build circuit at start, make sure orarray[0] is up end when /EXTENDED/, /BUILT/ circuit << eachor puts "Extended! cirnum = #{cirnum}, circuit length = #{circuit.count}" when /LAUNCHED/ sleep(1) puts x=cir_status.detect{|a| a =~ /^#{cirnum}/} circuit << eachor if (x =~ /BUILT/) or (x =~/EXTENDED/) end } [cirnum,circuit] end
This gets 3 bridge IP addresses from the Tor bridge website. proxyconfig is optional. default proxyconfig = {:type=>‘tor’ ,:port=>9050,:addr=>‘127.0.0.1’} The return format is fo the form:
[HTTPcode, [{:bridgeip, :bridgeport},{:bridgeip, :bridgeport},{:bridgeip, :bridgeport}]. The types can be one of ‘tor’, ‘polipo’, ‘socks’, ‘http’, ‘https’,nil, ‘none’.
Get 3 bridges
Tor::TController.get_bridges( {:proxytype=>'tor',:proxyport=>9050,:proxyaddr=>'127.0.0.1'} )
# File lib/tcontroller.rb, line 187 def get_bridges(cacheddesc, *config) url = URI.parse "https://bridges.torproject.org/" if config.empty? proxyconfig={:type=>'tor' ,:port=>9050,:addr=>'127.0.0.1'} else proxyconfig = config[0] end if ! defined?(@myhttperrors) @myhttperrors=0 end case proxyconfig[:type] when /none/,nil http_session=Net::HTTP.new(url.host,url.port) when /socks/,/tor/ http_session=Net::HTTP.SOCKSProxy(proxyconfig[:addr], proxyconfig[:port]).new(url.host,url.port) when /http/,/https/,/polipo/ http_session=Net::HTTP::Proxy(proxyconfig[:addr], proxyconfig[:port]).new(url.host,url.port) end if url.scheme=="https" http_session.use_ssl = true http_session.verify_mode = OpenSSL::SSL::VERIFY_NONE else http_session.use_ssl=false end bridges=[] # Rescue from http error begin resp = http_session.get2 url.path # Let Tor choose circuit itself # Additional code will be added shortly to attach the stream to the circuit directly puts "#{resp.code} HTTP response" #puts resp.body respcode= resp.code=="200" ? 200:nil if resp.code == "200" torbridgeip=resp.body.scan(/\d+\.\d+\.\d+\.\d+\:\d+/) torbridgeip.each{|eachbridge| bridgeip,bridgeport= eachbridge.split(':') x=Bridge.where(:ipaddr=>bridgeip, :port =>bridgeport) if x.empty? if cacheddesc.nil? or (bridge_geoip=cacheddesc.get_geoiprecord(bridgeip)).nil? Bridge.create(:ipaddr=>bridgeip, :port =>bridgeport, :lat=>0, :lng=>0) else Bridge.create(:ipaddr=>bridgeip, :port =>bridgeport, :lat=>bridge_geoip.latitude.to_f, :lng=>bridge_geoip.longitude.to_f ) end end } end bridges = torbridgeip rescue @myhttperrors +=1 respcode=nil bridges=[] end bridges.nil? ? [] : bridges #Return array of all bridges end
This gets enty guards from the control port of tor using the getinfo “entry-guards” command and returns just the fingerprints in an array.
Get entry-guards
Tor::TController.get_entryguards #=> ["$abc123","$def456"...]
# File lib/tcontroller.rb, line 282 def get_entryguards rslt = getinfo "entry-guards" reply = rslt.collect{|eachguard| case eachguard when / unusable/ nil else "$"+ eachguard.split(/[~ =]/)[0] end } reply.delete(nil) reply end
This returns the number of HTTP errors from the #get_bridges command
# File lib/tcontroller.rb, line 298 def get_httperrors @myhttperrors end
This gets the IP addresses for ORs based on the characteristics of the OR. Example of the properties include: Fast, Guard, HSDir, Named, Running, Stable, V2Dir, Valid, Exit.
Get all Exit, Fast and entry Guard ORs
Tor::TController.get_purposeip("Exit") #=> ["a.b.c.d","e.f.g.h"...] Tor::TController.get_purposeip("fast exit") Tor::TController.get_purposeip("Guard")
# File lib/tcontroller.rb, line 310 def get_purposeip(nodetype) reply=[] s = getinfo("ns/all") ctr_i= s.size / 2 ctr_i.times{|j| rip=s[j*2].split[6] x=Router.where(:ipaddr=>rip) if !x.empty? finprint = "$" + x[0].fingerprint matcharray=nodetype.split.collect{|eachtype| if eachtype.start_with? '!' matchme = eachtype[1..(eachtype.length-1)] rslt = !(s[j*2+1] =~ (/#{matchme}/)) rslt ? rslt : nil #return nil if false, or return the number else s[j*2+1] =~ (/#{eachtype}/) end } reply << finprint if !matcharray.include? nil end } reply end
This gets a configuration from Tor. The arguments of this command might be case sensitive. It returns the protocol error message if it fails.
Get the present ORport Tor is using
Tor::TController.getconf("ORPort") #=>"9001" Tor::TController.getconf("__LeaveStreamsUnattached") #=>"1" For more details, see [https://gitweb.torproject.org/torspec.git/blob/HEAD:/control-spec.txt]
# File lib/tcontroller.rb, line 342 def getconf(confname) send_command(:getconf, confname) reply=[] case msg=read_reply when /250 / msg.split(/250 \S*=/).last else msg end end
This method sends the “GETINFO” protocol message with the arguments.
Getting more information from Tor
Tor::TController.getinfo("ns/all")
# File lib/tcontroller.rb, line 359 def getinfo(*args) errorcode,ans = sr(:getinfo,*args) ans end
This returns an array of hash tables with the fingerprints of all onion routers that have not been marked as down.
Get the network status from Tor
Tor::TController.net_status => [ {:fingerprint=>"$ABCD"},{:fingerprint=>"$EFGH"}...]
# File lib/tcontroller.rb, line 369 def net_status case version when /0.2.[01]/ net_status1 when/0.2.2/ net_status2 end end
This returns all the new streams that have not been assigned to a circuit
# File lib/tcontroller.rb, line 253 def newstreams reply=[] getinfo("stream-status").each{|eachstream| case eachstream when /\d+ NEW 0/ # It might be simpler to use this /\d+ NEW/ reply << eachstream.scan(/\d+/)[0].to_i end } reply end
This sets a configuration in Tor to a value.
Setting Tor config
Tor::TController.setconf("__DisablePredictedCircuits",1) =>["OK", []] Tor::TController.setconf("__LeaveStreamsUnattached",1) =>["OK", []] Tor::TController.setconf("ORPort",9001) =>["OK", []]
# File lib/tcontroller.rb, line 429 def setconf(confname,value) sr(:setconf,"#{confname}=#{value}") end
This method sends “signals” protocol message with the arguments.
Reload Tor config from file
Tor::TController.signal("RELOAD") Tor::TController.signal("HUP")
# File lib/tcontroller.rb, line 439 def signal(args) reply=[] case (args) when /NEWNYM/,/CLEARDNSCACHE/,/RELOAD/,/HUP/,/DUMP/,/USR1/,/DEBUG/,/USR2/,/shutdown/,/INT/ errorcode,reply=sr(:signal, args) close if args =~ /shutdown/ or args =~ /INT/ when /HALT/,/TERM/ send_command(:signal, "HALT") close else puts "#{args} not recognised by the at library development. Sending the signal nonetheless" reply=sr(:signal,args) end reply end
This sends commands and returns the protocol response code along with an array containing the results with the following format [errorcode,[array_of_results]].
Send and receive commands.
Tor::TController.sr(:signal,"reload") Tor::TController.sr(:signal,"newnym")
# File lib/tcontroller.rb, line 462 def sr(command, *args) send_command(command, *args) reply=[] readterm=true while readterm do # Read as much as possible until '250 OK' or error 5YZ case msg=read_reply when /^250 OK/ readterm=false puts errorstate="OK" when /^250[\+,\-,\s]/ reply << msg.split(/^\d+[\+,\-,\s]\S*=/).last unless msg=~/250\+/ errorstate = "OK" when /^\./ when /^5\d\d / if errorstate == "OK" # if a "2yz " code came before this then msg is part of response reply << msg else puts msg errorstate = msg.match(/^5\d\d/) readterm=false end else reply << msg end end reply.delete(nil) reply = errorstate=="OK" ? reply : [] [errorstate,reply] end