Problems with resolv.rb && Queue/Thread

J

James F. Hranicky

--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 |
| (e-mail address removed) 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--
 
R

Robert Klemme

James F. Hranicky said:
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?

Not exactly. But your code has a subtle error. It should read

ARGV.each { |ipaddr|
threads << Thread.new( ipaddr ) { |local_ipaddr|
....
IP4NetAddr.new(local_ipaddr).all { |ip|
....

Otherwise the threads access the variable defined in the ARGV.each block
and thus errors might occur since you're not accessing the value you think
you are.

Kind regards

robert
 
J

James F. Hranicky

Not exactly. But your code has a subtle error. It should read

ARGV.each { |ipaddr|
threads << Thread.new( ipaddr ) { |local_ipaddr|
....
IP4NetAddr.new(local_ipaddr).all { |ip|
....

Otherwise the threads access the variable defined in the ARGV.each block
and thus errors might occur since you're not accessing the value you think
you are.

I tried it and got this with 1.8:

/usr/local/lib/ruby/1.8/resolv.rb:539:in `delete': undefined method `queue' for 6:Fixnum (NoMethodError)

1.9 seems unchanged.

Thanks...I'll keep scanning for un-thread-safe code.

Jim
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,065
Latest member
OrderGreenAcreCBD

Latest Threads

Top