lib/lan_scanner.rb in lan_scanner-0.0.3 vs lib/lan_scanner.rb in lan_scanner-0.0.4
- old
+ new
@@ -1,104 +1,116 @@
-
-require 'socket'
-require 'ostruct'
-require 'tty-which'
-require 'tmpdir'
-require 'nokogiri'
-
-
-require_relative 'lan_scanner/device'
-require_relative 'lan_scanner/version'
-
-module LanScanner
-
- # @return [Array<LanScanner::Device>] list of devices
- def self.scan_devices(network: nil)
- _ensure_nmap_available
- if network.nil?
- network = my_networks
- end
- network = [network] unless network.is_a? Array
- sn_xml_results = []
- tmp_file = "#{Dir.tmpdir}/nmap_scan_#{Random.random_number}.xml"
- # first we do an -sL scan, which also receives addresses from router/network cache,
- # that are not found by -sn scan when scanning for the complete network, but are found
- # with -sn scan, when scanning for this addresses explicitly
- #
- # so after this scan we scan for this addresses beneath the networks with -sn
- sl_xml_results = []
- network.each do |n|
- ['-sL'].each do |nmap_type|
- `nmap #{nmap_type} #{n} -oX "#{tmp_file}"`
- sl_xml_results.push File.read tmp_file
- File.delete tmp_file
- end
- end
- # here we scan for the received ip addresses from network cache
- sl_ips = _parse_nmap_xml sl_xml_results
- `nmap -sn #{sl_ips.map(&:remote_address).join(' ')} -oX "#{tmp_file}"`
- sn_xml_results.push File.read tmp_file
- # here we ping the networks (fast ping which does not detect all)
- network.each do |n|
- ['-sn'].each do |nmap_type|
- `nmap #{nmap_type} #{n} -oX "#{tmp_file}"`
- sn_xml_results.push File.read tmp_file
- File.delete tmp_file
- end
- end
- _parse_nmap_xml sn_xml_results
- end
-
- # get states of given addresses
- def self.scan_device_states addresses
- addresses = [addresses] unless addresses.is_a? Array
- tmp_file = "#{Dir.tmpdir}/nmap_scan_#{Random.random_number}.xml"
- `nmap -sn #{addresses.join(' ')} -oX "#{tmp_file}"`
- online_hosts = _parse_nmap_xml [File.read(tmp_file)]
- offline_addresses = addresses.reject { |a| online_hosts.map(&:remote_address).include?(a) }
- online_hosts + offline_addresses.map { |a| OpenStruct.new(remote_address: a, host_name: nil, state: 'down') }
- end
-
- def self.my_networks
- my_ip_addresses.map do |a|
- if a.include?('.')
- a.split('.')[0..2].join('.') + '.0/24'
- else
- raise "No support for IPv6 devices"
- end
- end
- end
-
- def self.my_ip_addresses
- Socket.ip_address_list.select { |ai| ai.ipv4? && !ai.ipv4_loopback? }.map(&:ip_address).uniq
- end
-
- private
-
- def self._ensure_nmap_available
- unless TTY::Which.exist?("nmap")
- raise "Command 'nmap' not available. Ensure nmap is installed and added to your PATH variable. https://nmap.org'"
- end
- end
-
- def self._parse_nmap_xml(xml_contents)
- results = {} # use special hash to avoid duplicates easier
- xml_contents.each do |xml_data|
- xml_obj = Nokogiri::XML(xml_data)
- xml_obj.xpath('//nmaprun/host').each do |host|
- remote_address = host.at('address')['addr']
- host_name = host.at('hostnames/hostname')&.[]('name')
- state = host.at('status')&.[]('state')
- if !results.key?(remote_address) || (results.key?(remote_address) && host_name || state == 'up')
- old_host_name = results[remote_address]&.host_name
- results[remote_address] = OpenStruct.new(remote_address: remote_address, host_name: host_name || old_host_name, state: state)
- end
- end
- end
- puts
- results.values.select do |r|
- r.state == 'up' || r.host_name
- end.map do |r|
- LanScanner::Device.new host_name: r.host_name, remote_address: r.remote_address, state: r.state
- end
- end
+
+require 'socket'
+require 'ostruct'
+require 'tty-which'
+require 'tmpdir'
+require 'nokogiri'
+
+
+require_relative 'lan_scanner/device'
+require_relative 'lan_scanner/version'
+
+module LanScanner
+
+ # @return [Array<LanScanner::Device>] list of devices
+ def self.scan_devices(network: nil)
+ _ensure_nmap_available
+ if network.nil?
+ network = my_networks
+ end
+ network = [network] unless network.is_a? Array
+ sn_xml_results = []
+ tmp_file = "#{Dir.tmpdir}/nmap_scan_#{Random.random_number}.xml"
+ # first we do an -sL scan, which also receives addresses from router/network cache,
+ # that are not found by -sn scan when scanning for the complete network, but are found
+ # with -sn scan, when scanning for this addresses explicitly
+ #
+ # so after this scan we scan for this addresses beneath the networks with -sn
+ sl_xml_results = []
+ network.each do |n|
+ ['-sL'].each do |nmap_type|
+ `nmap #{nmap_type} #{n} -oX "#{tmp_file}"`
+ sl_xml_results.push File.read tmp_file
+ File.delete tmp_file
+ end
+ end
+ # here we scan for the received ip addresses from network cache
+ sl_ips = _parse_nmap_xml sl_xml_results
+ `nmap -sn #{sl_ips.map(&:remote_address).join(' ')} -oX "#{tmp_file}"`
+ sn_xml_results.push File.read tmp_file
+ # here we ping the networks (fast ping which does not detect all)
+ network.each do |n|
+ ['-sn'].each do |nmap_type|
+ `nmap #{nmap_type} #{n} -oX "#{tmp_file}"`
+ sn_xml_results.push File.read tmp_file
+ File.delete tmp_file
+ end
+ end
+ _parse_nmap_xml sn_xml_results
+ end
+
+ # get states of given addresses
+ # @param [Boolean] expensive make expensive check for devices which were not found by fast check already
+ def self.scan_device_states addresses, expensive: false
+ addresses = [addresses] unless addresses.is_a? Array
+ tmp_file = "#{Dir.tmpdir}/nmap_scan_#{Random.random_number}.xml"
+ nmap_scan_option = if expensive
+ '-Pn'
+ else
+ '-sn'
+ end
+ `nmap -sn #{addresses.join(' ')} -oX "#{tmp_file}"`
+ online_hosts = _parse_nmap_xml [File.read(tmp_file)]
+ offline_addresses = addresses.reject { |a| online_hosts.map(&:remote_address).include?(a) }
+ # check offline addresses again with expensive check
+ if expensive
+ `nmap -Pn #{offline_addresses.join(' ')} -oX "#{tmp_file}"`
+ online_hosts += _parse_nmap_xml [File.read(tmp_file)]
+ offline_addresses = addresses.reject { |a| online_hosts.map(&:remote_address).include?(a) }
+ end
+ online_hosts + offline_addresses.map { |a| OpenStruct.new(remote_address: a, host_name: nil, state: 'down') }
+ end
+
+ def self.my_networks
+ my_ip_addresses.map do |a|
+ if a.include?('.')
+ a.split('.')[0..2].join('.') + '.0/24'
+ else
+ raise "No support for IPv6 devices"
+ end
+ end
+ end
+
+ def self.my_ip_addresses
+ Socket.ip_address_list.select { |ai| ai.ipv4? && !ai.ipv4_loopback? }.map(&:ip_address).uniq
+ end
+
+ private
+
+ def self._ensure_nmap_available
+ unless TTY::Which.exist?("nmap")
+ raise "Command 'nmap' not available. Ensure nmap is installed and added to your PATH variable. https://nmap.org'"
+ end
+ end
+
+ def self._parse_nmap_xml(xml_contents)
+ results = {} # use special hash to avoid duplicates easier
+ xml_contents.each do |xml_data|
+ xml_obj = Nokogiri::XML(xml_data)
+ xml_obj.xpath('//nmaprun/host').each do |host|
+ remote_address = host.at('address')['addr']
+ host_name = host.at('hostnames/hostname')&.[]('name')
+ state = host.at('status')&.[]('state')
+ if !results.key?(remote_address) || (results.key?(remote_address) && host_name || state == 'up')
+ old_host_name = results[remote_address]&.host_name
+ results[remote_address] = OpenStruct.new(remote_address: remote_address, host_name: host_name || old_host_name, state: state)
+ end
+ end
+ end
+ puts
+ results.values.select do |r|
+ r.state == 'up' || r.host_name
+ end.map do |r|
+ LanScanner::Device.new host_name: r.host_name, remote_address: r.remote_address, state: r.state
+ end
+ end
end
\ No newline at end of file