From: "James F. Hranicky" Date: 2004-01-23T23:57:52+09:00 Subject: Problems with resolv.rb && Queue/Thread --Multipart_Fri__23_Jan_2004_09:57:48_-0500_002db620 Content-Type: text/plain; charset=US-ASCII Content-Transfer-Encoding: 7bit Platform: Solaris 8 Ruby Versions: 1.8.0/2003-08-04 1.9.0/2004-01-13 I'm writing a simple port scanner, and it uses multiple threads to separate out the scanning functions and a Queue to collect the scan info. I was attempting to use Resolv.getname within each thread to lookup the hostname of a given IP, and I'm getting errors from within the Resolv module: % ruby scratch/scan -p 25 10.0.2.{1,3,5,7,9,11}.{0,32,64,96,128,160,192,224}/27 | m /usr/local/lib/ruby/1.8/resolv.rb:539:in `delete': undefined method `queue' for 7:Fixnum (NoMethodError) from /usr/local/lib/ruby/1.8/resolv.rb:539:in `delete_if' from /usr/local/lib/ruby/1.8/resolv.rb:539:in `delete' from /usr/local/lib/ruby/1.8/resolv.rb:482:in `each_resource' from /usr/local/lib/ruby/1.8/resolv.rb:441:in `each_name' from /usr/local/lib/ruby/1.8/resolv.rb:258:in `each_name' from /usr/local/lib/ruby/1.8/resolv.rb:257:in `each' from /usr/local/lib/ruby/1.8/resolv.rb:257:in `each_name' from /usr/local/lib/ruby/1.8/resolv.rb:245:in `getname' ... 11 levels... from scratch/scan:53:in `Main' from scratch/scan:52:in `each' from scratch/scan:52:in `Main' from scratch/scan:124 The above error is relatively consistent with 1.8, but not 1.9: /usr/local/lib/ruby/1.9/resolv.rb:539:in `delete': undefined method `queue' for 7:Fixnum (NoMethodError) /usr/local/lib/ruby/1.9/resolv.rb:539:in `delete': undefined method `queue' for nil:NilClass (NoMethodError) /usr/local/lib/ruby/1.9/resolv.rb:539:in `delete'/cise/homes/jfh/scratch/scan:63: [BUG] Segmentation fault Here is the offending code: ARGV.each { |ipaddr| threads << Thread.new { ips = Hash.new { |h,k| h[k] = [] } IP4NetAddr.new(ipaddr).all { |ip| # # Lookup name # begin ---> timeout(0.2) { puts Resolv.getname(ip) } rescue Timeout::Error, Resolv::ResolvError end When I comment out the code here and move it back out to the main thread, the problem does not occur. Also, the problem does not seem to occur on FreeBSD 4.9 or Mandrake Linux 9.1. Attached is the scanner and netaddr.rb (at some point I want to patch ipaddr.rb to be able to do each and all, but I haven't gotten around to it yet). Anyone know what's going on? ---------------------------------------------------------------------- | Jim Hranicky, Senior SysAdmin UF/CISE Department | | E314D CSE Building Phone (352) 392-1499 | | jfh@cise.ufl.edu http://www.cise.ufl.edu/~jfh | ---------------------------------------------------------------------- About politics: Don't worry about results It's the thought that counts --Multipart_Fri__23_Jan_2004_09:57:48_-0500_002db620 Content-Type: text/plain; name="scan.txt" Content-Disposition: attachment; filename="scan.txt" Content-Transfer-Encoding: 7bit #!/local/bin/ruby require 'netaddr' require 'socket' require 'getoptlong' require 'timeout' require 'resolv' require 'thread' def usage puts "bad usage" end def Main threads = [] aq = Queue.new ports = portslist = nil port_ranges = [] parser = GetoptLong.new parser.set_options( [ '--ports', '-p', GetoptLong::REQUIRED_ARGUMENT ] ) begin parser.each_option { |opt, arg| case opt when '--ports' ports = arg end } rescue GetoptLong::InvalidOption usage() end raise "No ports specified" unless ports portslist = ports.split(/,/) portslist.each { |pt| case pt when /^\d+$/ port_ranges << Range.new(pt.to_i, pt.to_i) when /^(\d+)\-(\d+)$/ port_ranges << Range.new($1.to_i, $2.to_i) else raise "Invalid port or port range: #{pt}" end } Thread.abort_on_exception = true ARGV.each { |ipaddr| threads << Thread.new { ips = Hash.new { |h,k| h[k] = [] } IP4NetAddr.new(ipaddr).all { |ip| # # Lookup name # begin timeout(0.2) { puts Resolv.getname(ip) } rescue Timeout::Error, Resolv::ResolvError end port_ranges.each { |range| range.each { |port| # # Attempt to connect # begin timeout(0.2) { TCPSocket.new(ip, port) } ips[ip] << port rescue Errno::ECONNREFUSED, Timeout::Error, Errno::ENETUNREACH, Errno::EHOSTUNREACH, Errno::EINVAL, Errno::EACCES next end } } } #ips.keys.sort.each { |k| aq << ips #} } } threads.each { |t| t.join } a = [] while (!aq.empty?) do a << aq.pop() end lines = [] a.each { |ips| ips.keys.sort.each { |ip| ipline = sprintf "%-18s", ip hostname = nil # # Lookup name # begin timeout(0.2) { hostname = Resolv.getname(ip) } rescue Timeout::Error, Resolv::ResolvError end ipline << sprintf(" (%s)", hostname) if hostname lines << sprintf("%-65s : %s", ipline, ips[ip].join(",")) } } lines.sort.each { |line| puts line } end Main() --Multipart_Fri__23_Jan_2004_09:57:48_-0500_002db620 Content-Type: text/plain; name="netaddr.rb.txt" Content-Disposition: attachment; filename="netaddr.rb.txt" Content-Transfer-Encoding: 7bit class IP4NetAddr attr_accessor :network, :mask, :maskBits, :broadcast, :wildcard, :numIPs, :numHostIPs attr_accessor :netn, :maskn, :wildcardn, :broadcastn def IP4NetAddr.inet_aton(ip) raise "Invalid ip: #{ip}" unless (ip =~ /\d+\.\d+\.\d+\.\d+/) n = 0 shiftlen = 24 ip.scan(/\d+/) { |octet| n += (octet.to_i << shiftlen) shiftlen -= 8 } n end def IP4NetAddr.inet_ntoa(n) raise "Invalid numeric IP value: #{n}" unless n >= 0 && n <= 2**32 octets = Array.new shiftlen = 24 4.times { octets << ( (n & (255 << shiftlen)) >> shiftlen) shiftlen -= 8 } octets.join(".") end def initialize(*argv) network, mask = argv network, mask = network.split("/") unless mask mask = mask.to_i raise "Invalid net or mask" unless (network =~ /\d+\.\d+\.\d+\.\d+/ && (mask.to_s =~ /^\d{1,2}$/ || mask =~ /\d+\.\d+\.\d+\.\d+/)) @network = network if (mask.to_s =~ /^\d{1,2}$/) @maskBits = mask @mask = bitsToMask(mask) else @mask = mask @maskBits = maskToBits(mask) end @numIPs = 2 ** (32 - maskBits) @numHostIPs = @numIPs - 2 @netn = IP4NetAddr.inet_aton(@network) @maskn = IP4NetAddr.inet_aton(@mask) @wildcardn = @maskn ^ ((2 ** 32) - 1) @broadcastn = @netn ^ @wildcardn @broadcast = IP4NetAddr.inet_ntoa(@broadcastn) @wildcard = IP4NetAddr.inet_ntoa(@wildcardn) if (@netn & @wildcardn != 0) raise "Invalid netmask" end yeild self if block_given? end def maskToBits(mask) bits = 0 mask.scan(/\d+/) { |octet| 7.downto(0) { |i| bits += 1 if (octet.to_i & ( 1 << i)) != 0 } } bits end def bitsToMask(bits) octets = Array.new 4.times { if ( bits > 8 ) octets << 255 elsif ( bits <= 0 ) octets << 0 else octets << (((2 ** bits) - 1) << 8 - bits) end bits -= 8 } octets.join(".") end def eachIPAddr @netn.upto(@broadcastn) { |ipn| yield IP4NetAddr.inet_ntoa(ipn) } end def eachIPNumber @netn.upto(@broadcastn) { |ipn| yield ipn } end def eachHostIPAddr (@netn + 1).upto(@broadcastn - 1) { |ipn| yield IP4NetAddr.inet_ntoa(ipn) } end def eachHostIPNumber (@netn + 1).upto(@broadcastn - 1) { |ipn| yield ipn } end def contains(ip) ipn = IP4NetAddr.inet_aton(ip) return true if ipn >= @netn && ipn <= broadcastn return false end alias :all :eachIPAddr alias :each :eachHostIPAddr end if $0 == __FILE__ n = IP4NetAddr.new("128.227.205.0", 29) puts "Net: #{n.network}" puts "Mask: #{n.mask}" puts "MaskBits: #{n.maskBits}" puts sprintf("netn: %x", n.netn) print "ntoa netn: ", IP4NetAddr.inet_ntoa(n.netn), "\n" puts sprintf("nmaskn: %x", n.maskn) puts sprintf("nwildcardn: %08x", n.wildcardn) print "ntoa broadcastn: ", IP4NetAddr.inet_ntoa(n.broadcastn), "\n" n.eachIPAddr { |ip| p ip } puts "Host IPS:" n.eachHostIPAddr { |ip| p ip } end --Multipart_Fri__23_Jan_2004_09:57:48_-0500_002db620--