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--
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--