netcrawl 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/README.md +86 -0
- data/Rakefile +47 -0
- data/bin/netcrawl +9 -0
- data/lib/netcrawl.rb +63 -0
- data/lib/netcrawl/.cli.rb.swp +0 -0
- data/lib/netcrawl/cli.rb +59 -0
- data/lib/netcrawl/config.rb +37 -0
- data/lib/netcrawl/dns.rb +53 -0
- data/lib/netcrawl/method/cdp.rb +33 -0
- data/lib/netcrawl/method/lldp.rb +35 -0
- data/lib/netcrawl/namemap.rb +12 -0
- data/lib/netcrawl/output.rb +72 -0
- data/lib/netcrawl/output/dot.rb +53 -0
- data/lib/netcrawl/pollmap.rb +13 -0
- data/lib/netcrawl/snmp.rb +68 -0
- data/netcrawl.gemspec +18 -0
- metadata +119 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
# Netcrawl
|
2
|
+
Given snmp community and single device, crawls the network via discovering
|
3
|
+
LLDP/CDP neighbours, while producing list or dot file (for graphviz digraphs)
|
4
|
+
|
5
|
+
## Install
|
6
|
+
% gem install netcrawl
|
7
|
+
|
8
|
+
## Use
|
9
|
+
% netcrawl --graphiz router.example.com
|
10
|
+
|
11
|
+
## Command line
|
12
|
+
```
|
13
|
+
Usage: netcrawl [options] hostname
|
14
|
+
-g, --graphviz dot output use 'dot -Tpng -o map.png map.dot'
|
15
|
+
-l, --list list nodes
|
16
|
+
-j, --json json output
|
17
|
+
-y, --yaml yaml output
|
18
|
+
-a, --hash hash/associative array output
|
19
|
+
-r, --resolve resolve addresses to names
|
20
|
+
-p, --purge remove peers not in configured CIDR
|
21
|
+
-c, --community SNMP community to use
|
22
|
+
-d, --debug turn debugging on
|
23
|
+
-h, --help Display this help message.
|
24
|
+
|
25
|
+
```
|
26
|
+
* graphiz - graphis (dot) output
|
27
|
+
* list - list nodes found
|
28
|
+
* json - json output
|
29
|
+
* yaml - yaml output
|
30
|
+
* hash - ruby hash output
|
31
|
+
* resolve - resolve IP addresses
|
32
|
+
* purge - remove non-cidr matching peers from output
|
33
|
+
* community - sets snmp community
|
34
|
+
* debug - turn on debugging
|
35
|
+
|
36
|
+
## Config
|
37
|
+
```
|
38
|
+
---
|
39
|
+
use:
|
40
|
+
- LLDP
|
41
|
+
- CDP
|
42
|
+
poll:
|
43
|
+
- 192.0.2.0/24
|
44
|
+
snmp:
|
45
|
+
community: public
|
46
|
+
timeout: 1
|
47
|
+
retries: 2
|
48
|
+
bulkrows: 35
|
49
|
+
dot:
|
50
|
+
bothlinks: true
|
51
|
+
color:
|
52
|
+
- - cpe
|
53
|
+
- gold
|
54
|
+
- - -sw
|
55
|
+
- blue
|
56
|
+
- - -pe
|
57
|
+
- red
|
58
|
+
- - ' -p'
|
59
|
+
- yellow
|
60
|
+
dns:
|
61
|
+
afi:
|
62
|
+
log: STDERR
|
63
|
+
debug: false
|
64
|
+
namemap:
|
65
|
+
- - -re\d+
|
66
|
+
- ''
|
67
|
+
- - (.*(?<!as23456.net)$)
|
68
|
+
- \1.as23456.net
|
69
|
+
```
|
70
|
+
|
71
|
+
* use - methods to use for crawling
|
72
|
+
* poll - cidrs to allow snmp for
|
73
|
+
* snmp community - snmp community to use
|
74
|
+
* snmp timout - snmp timout in seconds
|
75
|
+
* snmp retries - snmp retries count
|
76
|
+
* snmp bulkrows - snmp row count for bulkget
|
77
|
+
* dot bothlinks - show a-b and b-a link
|
78
|
+
* dot color - regexp to color, first hit used
|
79
|
+
* dns afi - ipv4/ipv6 or nil
|
80
|
+
* log - STDERR/STDOUT or file
|
81
|
+
* debug - debugging
|
82
|
+
* namemap - map (LLDP) name to FQDN (JunOS does not give domain)
|
83
|
+
|
84
|
+
## Library use
|
85
|
+
require 'netcrawl'
|
86
|
+
output = NetCrawl.new.crawl('192.0.2.1').to_hash
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
begin
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'bundler'
|
4
|
+
Bundler.setup
|
5
|
+
rescue LoadError
|
6
|
+
warn 'bunler missing'
|
7
|
+
exit 42
|
8
|
+
end
|
9
|
+
|
10
|
+
gemspec = eval(File.read(Dir['*.gemspec'].first))
|
11
|
+
file = [gemspec.name, gemspec.version].join('-') + '.gem'
|
12
|
+
|
13
|
+
desc 'Validate gemspec'
|
14
|
+
task :gemspec do
|
15
|
+
gemspec.validate
|
16
|
+
end
|
17
|
+
|
18
|
+
desc 'Run minitest'
|
19
|
+
task :test do
|
20
|
+
Rake::TestTask.new do |t|
|
21
|
+
t.libs.push "lib"
|
22
|
+
t.test_files = FileList['spec/*_spec.rb']
|
23
|
+
t.verbose = true
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Build gem'
|
28
|
+
task :build do
|
29
|
+
system "gem build #{gemspec.name}.gemspec"
|
30
|
+
FileUtils.mkdir_p 'gems'
|
31
|
+
FileUtils.mv file, 'gems'
|
32
|
+
end
|
33
|
+
|
34
|
+
desc 'Install gem'
|
35
|
+
task :install => :build do
|
36
|
+
system "sudo -E sh -c \'umask 022; gem install gems/#{file}\'"
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'Remove gems'
|
40
|
+
task :clean do
|
41
|
+
FileUtils.rm_rf 'gems'
|
42
|
+
end
|
43
|
+
|
44
|
+
desc 'Push to rubygems'
|
45
|
+
task :push do
|
46
|
+
system "gem push gems/#{file}"
|
47
|
+
end
|
data/bin/netcrawl
ADDED
data/lib/netcrawl.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
require_relative 'netcrawl/config'
|
2
|
+
class NetCrawl
|
3
|
+
class NetCrawlError < StandardError; end
|
4
|
+
class MethodNotFound < NetCrawlError; end
|
5
|
+
attr_reader :hosts
|
6
|
+
|
7
|
+
# @param [String] host host to start crawl from
|
8
|
+
# @return [NetCrawl::Output]
|
9
|
+
def crawl host
|
10
|
+
recurse host
|
11
|
+
Output.new @hosts, @poll
|
12
|
+
end
|
13
|
+
|
14
|
+
# @param [String] host host to get list of peers from
|
15
|
+
# @return [Array] list of peers seen connected to host
|
16
|
+
def get host
|
17
|
+
peers = []
|
18
|
+
@methods.each do |method|
|
19
|
+
peers += method.send(:get, host)
|
20
|
+
end
|
21
|
+
peers.uniq
|
22
|
+
end
|
23
|
+
|
24
|
+
# Given string of IP address, recurses through peers seen and populates @hosts hash
|
25
|
+
# @param [String] host host to start recurse from
|
26
|
+
# @return [void]
|
27
|
+
def recurse host
|
28
|
+
peers = get host
|
29
|
+
@hosts[host] = peers
|
30
|
+
peers.each do |peer|
|
31
|
+
next if @hosts.has_key? peer
|
32
|
+
next unless @poll.include? peer
|
33
|
+
crawl peer
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def initialize
|
40
|
+
@methods = []
|
41
|
+
@hosts = {}
|
42
|
+
@poll = PollMap.new
|
43
|
+
CFG.use.each do |method|
|
44
|
+
begin
|
45
|
+
method = File.basename method.to_s
|
46
|
+
file = 'netcrawl/method/' + method.downcase
|
47
|
+
require_relative file
|
48
|
+
@methods.push NetCrawl.const_get(method)
|
49
|
+
rescue NameError, LoadError => error
|
50
|
+
raise MethodNotFound, "unable to find method '#{method}'"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
end
|
57
|
+
require_relative 'netcrawl/pollmap'
|
58
|
+
require_relative 'netcrawl/namemap'
|
59
|
+
require_relative 'netcrawl/pollmap'
|
60
|
+
require_relative 'netcrawl/method/cdp'
|
61
|
+
require_relative 'netcrawl/method/lldp'
|
62
|
+
require_relative 'netcrawl/dns'
|
63
|
+
require_relative 'netcrawl/output'
|
Binary file
|
data/lib/netcrawl/cli.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require_relative '../netcrawl'
|
2
|
+
require 'slop'
|
3
|
+
|
4
|
+
class NetCrawl
|
5
|
+
class CLI
|
6
|
+
class MissingHost < NetCrawlError; end
|
7
|
+
class NoConfig < NetCrawlError; end
|
8
|
+
|
9
|
+
def run
|
10
|
+
output = NetCrawl.new.crawl @host
|
11
|
+
output.clean if @opts[:purge]
|
12
|
+
output.resolve if @opts[:resolve]
|
13
|
+
if @opts[:graphviz]
|
14
|
+
output.to_dot
|
15
|
+
elsif @opts[:list]
|
16
|
+
output.to_list
|
17
|
+
elsif @opts[:json]
|
18
|
+
output.to_json
|
19
|
+
elsif @opts[:yaml]
|
20
|
+
output.to_yaml
|
21
|
+
else
|
22
|
+
output.to_hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
if Config.system.empty? and Config.user.empty?
|
30
|
+
Config.user = Config.default
|
31
|
+
Config.save :user
|
32
|
+
raise NoConfig, 'edit ~/.config/netcrawl/config'
|
33
|
+
end
|
34
|
+
@opts = opt_parse
|
35
|
+
args = @opts.parse
|
36
|
+
@host = DNS.getip args.shift
|
37
|
+
CFG.snmp.community = @opts[:community] if @opts[:community]
|
38
|
+
CFG.debug = true if @opts[:debug]
|
39
|
+
raise MissingHost, 'no hostname given as argument' unless @host
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def opt_parse
|
44
|
+
opts = Slop.parse(:help=>true) do
|
45
|
+
banner 'Usage: netcrawl [options] hostname'
|
46
|
+
on 'g', 'graphviz', 'dot output use \'dot -Tpng -o map.png map.dot\''
|
47
|
+
on 'l', 'list', 'list nodes'
|
48
|
+
on 'j', 'json', 'json output'
|
49
|
+
on 'y', 'yaml', 'yaml output'
|
50
|
+
on 'a', 'hash', 'hash/associative array output'
|
51
|
+
on 'r', 'resolve', 'resolve addresses to names'
|
52
|
+
on 'p', 'purge', 'remove peers not in configured CIDR'
|
53
|
+
on 'c=', 'community', 'SNMP community to use'
|
54
|
+
on 'd', 'debug', 'turn debugging on'
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'asetus'
|
2
|
+
require 'logger'
|
3
|
+
class NetCrawl
|
4
|
+
Config = Asetus.new :name=>'netcrawl', :load=>false
|
5
|
+
Config.default.use = %w(LLDP CDP)
|
6
|
+
Config.default.poll = [ # addresses accepted for polling
|
7
|
+
'192.0.2.0/24',
|
8
|
+
'198.51.100.0/24',
|
9
|
+
'0.0.0.0/0',
|
10
|
+
]
|
11
|
+
Config.default.snmp.community = 'public'
|
12
|
+
Config.default.snmp.timeout = 1
|
13
|
+
Config.default.snmp.retries = 2
|
14
|
+
Config.default.snmp.bulkrows = 35 # 1500B packet should fit about 50 :cdpCacheAddress rows
|
15
|
+
Config.default.dot.bothlinks = false # keep both a-b and b-a links
|
16
|
+
Config.default.dot.color = [ # regexp of host => color
|
17
|
+
[ 'cpe', 'gold' ],
|
18
|
+
[ '-sw', 'blue' ],
|
19
|
+
[ '-pe', 'red' ],
|
20
|
+
[' -p', 'yellow' ],
|
21
|
+
]
|
22
|
+
Config.default.dns.afi = nil # could be 'ipv4' or 'ipv6'
|
23
|
+
Config.default.log = 'STDERR'
|
24
|
+
Config.default.debug = false
|
25
|
+
Config.default.namemap = [ # regexp match+sub of hostname (needed for LLDP)
|
26
|
+
['-re\d+', ''],
|
27
|
+
['^KILLME(.*(?<!my.domain.com)$)', '\1.my.domain.com'], #adds missing domain name
|
28
|
+
]
|
29
|
+
Config.load
|
30
|
+
CFG = Config.cfg
|
31
|
+
log = CFG.log
|
32
|
+
log = STDERR if log == 'STDERR'
|
33
|
+
log = STDOUT if log == 'STDOUT'
|
34
|
+
Log = Logger.new log
|
35
|
+
Log.level = Logger::INFO
|
36
|
+
Log.level = Logger::DEBUG if CFG.debug
|
37
|
+
end
|
data/lib/netcrawl/dns.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'resolv'
|
2
|
+
class NetCrawl
|
3
|
+
class Resolve
|
4
|
+
|
5
|
+
# @param [String] name DNS name which we try to resolve to IP
|
6
|
+
# @return [String, nil] string if name resolves to IP, otherwise nil
|
7
|
+
def getip name
|
8
|
+
if @cacheip.has_key? name
|
9
|
+
@cacheip[name]
|
10
|
+
else
|
11
|
+
ip = nil
|
12
|
+
begin
|
13
|
+
if CFG.dns.afi == 'ipv4'
|
14
|
+
ip = Resolv::DNS.new.getresource(name, Resolv::DNS::Resource::IN::A).address
|
15
|
+
elsif CFG.dns.afi == 'ipv6'
|
16
|
+
ip = Resolv::DNS.new.getresource(name, Resolv::DNS::Resource::IN::AAAA).address
|
17
|
+
else
|
18
|
+
ip = Resolv.getaddress name
|
19
|
+
end
|
20
|
+
rescue => error
|
21
|
+
Log.debug "DNS resolution for '#{name}' raised error '#{error.class}' with message '#{error.message}'"
|
22
|
+
return nil
|
23
|
+
end
|
24
|
+
@cacheip[name] = ip
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @param [String] ip DNS IP which we try to resolve to name
|
29
|
+
# @return [String] name if it resolves, ip otherwise
|
30
|
+
def getname ip
|
31
|
+
if @cachename.has_key? ip
|
32
|
+
@cachename[ip]
|
33
|
+
else
|
34
|
+
name = nil
|
35
|
+
begin
|
36
|
+
name = Resolv.getname ip
|
37
|
+
rescue => error
|
38
|
+
Log.debug "DNS resolution for '#{ip}' raised error '#{error.class}' with message '#{error.message}'"
|
39
|
+
name = ip
|
40
|
+
end
|
41
|
+
@cachename[ip] = name
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def initialize
|
48
|
+
@cacheip = {}
|
49
|
+
@cachename = {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
DNS = Resolve.new
|
53
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative '../snmp'
|
2
|
+
class NetCrawl
|
3
|
+
class CDP
|
4
|
+
OID = {
|
5
|
+
# http://tools.cisco.com/Support/SNMP/do/BrowseOID.do?local=en&translate=Translate&objectInput=1.3.6.1.4.1.9.9.23.1.2.1.1
|
6
|
+
:cdpCacheAddress => '1.3.6.1.4.1.9.9.23.1.2.1.1.4',
|
7
|
+
:cdpCacheDeviceId => '1.3.6.1.4.1.9.9.23.1.2.1.1.6',
|
8
|
+
}
|
9
|
+
|
10
|
+
# @param [String] host host to query
|
11
|
+
# @return [Hash] neighbor information
|
12
|
+
def self.get host
|
13
|
+
cdp = new(host)
|
14
|
+
cdp.peers
|
15
|
+
end
|
16
|
+
|
17
|
+
def peers
|
18
|
+
@snmp.bulkwalk(OID[:cdpCacheAddress]).map do |vb|
|
19
|
+
vb.as_ip
|
20
|
+
end
|
21
|
+
rescue SNMP::NoResponse
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def initialize host
|
28
|
+
@snmp = SNMP.new host
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require_relative '../snmp'
|
2
|
+
class NetCrawl
|
3
|
+
class LLDP
|
4
|
+
include NameMap
|
5
|
+
OID = {
|
6
|
+
# http://standards.ieee.org/getieee802/download/802.1AB-2009.pdf
|
7
|
+
:lldpRemChassisIdSubtype => '1.0.8802.1.1.2.1.4.1.1.4', # CSCO and JNPR use 4 (MAC address) rendering ChassisID useless
|
8
|
+
:lldpRemChassisId => '1.0.8802.1.1.2.1.4.1.1.5',
|
9
|
+
:lldpRemSysName => '1.0.8802.1.1.2.1.4.1.1.9',
|
10
|
+
}
|
11
|
+
|
12
|
+
# @param [String] host host to query
|
13
|
+
# @return [Hash] neighbor information
|
14
|
+
def self.get host
|
15
|
+
lldp = new(host)
|
16
|
+
lldp.peers
|
17
|
+
end
|
18
|
+
|
19
|
+
def peers
|
20
|
+
@snmp.bulkwalk(OID[:lldpRemSysName]).map do |vb|
|
21
|
+
DNS.getip namemap(vb.value)
|
22
|
+
end.compact
|
23
|
+
rescue SNMP::NoResponse
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize host
|
30
|
+
@snmp = SNMP.new host
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
class NetCrawl
|
2
|
+
class Output
|
3
|
+
attr_reader :iphash, :namehash, :hash
|
4
|
+
|
5
|
+
# @return [String] pretty print of hash
|
6
|
+
def to_hash
|
7
|
+
require 'pp'
|
8
|
+
out = ''
|
9
|
+
PP.pp @hash, out
|
10
|
+
out
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [String] yaml of hosts and peers found
|
14
|
+
def to_yaml
|
15
|
+
require 'yaml'
|
16
|
+
YAML.dump @hash
|
17
|
+
end
|
18
|
+
|
19
|
+
# @return [String] json of hosts and peers found
|
20
|
+
def to_json
|
21
|
+
require 'json'
|
22
|
+
JSON.pretty_generate @hash
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [Array] of nodes found
|
26
|
+
def to_list
|
27
|
+
nodes = []
|
28
|
+
@hash.each do |host, peers|
|
29
|
+
nodes << host
|
30
|
+
nodes << peers
|
31
|
+
end
|
32
|
+
nodes.flatten.uniq.sort
|
33
|
+
end
|
34
|
+
|
35
|
+
# resolves ip addresses and changes @hash to point to the resolved hash
|
36
|
+
# @return [void]
|
37
|
+
def resolve
|
38
|
+
@namehash = {}
|
39
|
+
@iphash.each do |host, peers|
|
40
|
+
host = DNS.getname host
|
41
|
+
@namehash[host] = []
|
42
|
+
peers.each do |peer|
|
43
|
+
@namehash[host].push DNS.getname(peer)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
@hash = @namehash
|
47
|
+
end
|
48
|
+
|
49
|
+
# remove peers not matchin configured CIDR
|
50
|
+
def clean
|
51
|
+
@hash.each do |host, peers|
|
52
|
+
peers = peers.delete_if{|peer|not @pollmap.include? peer}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def initialize hash, pollmap
|
59
|
+
@iphash = hash
|
60
|
+
@hash = @iphash
|
61
|
+
@pollmap = pollmap
|
62
|
+
end
|
63
|
+
|
64
|
+
def method_missing name, *args
|
65
|
+
raise NoMethodError, "invalid method #{name} for #{inspect}:#{self.class}" unless name.match(/to_.*/)
|
66
|
+
output = File.basename name[3..-1]
|
67
|
+
require_relative 'output/' + output
|
68
|
+
output = NetCrawl::Output.const_get output.capitalize
|
69
|
+
output.send :output, self
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
class NetCrawl
|
2
|
+
class Output
|
3
|
+
class Dot
|
4
|
+
INDENT = ' ' * 2
|
5
|
+
DEFAULT_COLOR = 'black'
|
6
|
+
def self.output output
|
7
|
+
new(output).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
str = "graph NetCrawl {\n"
|
12
|
+
@output.to_list.each do |host|
|
13
|
+
str << INDENT + id(host) + "[color=\"#{color(host)}\"]\n"
|
14
|
+
str << INDENT + id(host) + "[label=\"#{host}\"]\n"
|
15
|
+
if @hash.has_key? host
|
16
|
+
@hash[host].each do |peer|
|
17
|
+
next if not CFG.dot.bothlinks and @connections.include?([peer, host].sort)
|
18
|
+
@connections << [peer, host].sort
|
19
|
+
str << INDENT + INDENT + id(host) + ' -- ' + id(peer) + "\n"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
str << "}\n"
|
24
|
+
str
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize output
|
30
|
+
@output = output
|
31
|
+
@connections = []
|
32
|
+
@hash = @output.hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def id host
|
36
|
+
host = host.gsub(/[-.]/, '_')
|
37
|
+
'_' + host
|
38
|
+
end
|
39
|
+
|
40
|
+
def color host
|
41
|
+
color = nil
|
42
|
+
CFG.dot.color.each do |re, clr|
|
43
|
+
if host.match re
|
44
|
+
color = clr
|
45
|
+
break
|
46
|
+
end
|
47
|
+
end
|
48
|
+
color or DEFAULT_COLOR
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'snmp'
|
2
|
+
class NetCrawl
|
3
|
+
class SNMP
|
4
|
+
class NoResponse < NetCrawlError; end
|
5
|
+
# Closes the SNMP connection
|
6
|
+
# @return [void]
|
7
|
+
def close
|
8
|
+
@snmp.close
|
9
|
+
end
|
10
|
+
|
11
|
+
# Gets one oid, return value
|
12
|
+
# @param [String] oid to get
|
13
|
+
# @return [SNMP::VarBind]
|
14
|
+
def get oid
|
15
|
+
mget([oid]).first
|
16
|
+
end
|
17
|
+
|
18
|
+
# Get multiple oids, return array of values
|
19
|
+
# @param [Array(String)] oids to get
|
20
|
+
# @return [SNMP::VarBindList]
|
21
|
+
def mget oids
|
22
|
+
snmp :get, oids
|
23
|
+
end
|
24
|
+
|
25
|
+
# Bulkwalk everything below root oid
|
26
|
+
# @param [String] root oid to start from
|
27
|
+
# @return [Array(SNMP::VarBind)]
|
28
|
+
def bulkwalk root
|
29
|
+
last, oid, results = false, root.dup, []
|
30
|
+
root = root.split('.').map{|chr|chr.to_i}
|
31
|
+
while not last
|
32
|
+
vbs = snmp(:get_bulk, 0, CFG.snmp.bulkrows, oid).varbind_list
|
33
|
+
vbs.each do |vb|
|
34
|
+
oid = vb.oid
|
35
|
+
(last = true; break) if not oid[0..root.size-1] == root
|
36
|
+
results.push vb
|
37
|
+
end
|
38
|
+
end
|
39
|
+
results
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def initialize host, community=CFG.snmp.community, timeout=CFG.snmp.timeout, retries=CFG.snmp.retries
|
45
|
+
@host = host
|
46
|
+
@snmp = ::SNMP::Manager.new :Host=>@host, :Community=>community,
|
47
|
+
:Timeout=>timeout, :Retries=>retries,
|
48
|
+
:MibModules=>[]
|
49
|
+
end
|
50
|
+
|
51
|
+
def snmp cmd, *args
|
52
|
+
@snmp.send cmd, *args
|
53
|
+
rescue ::SNMP::RequestTimeout, Errno::EACCES => error
|
54
|
+
msg = "host '#{@host}' raised '#{error.class}' with message '#{error.message}' for method '#{cmd}' with args '#{args}'"
|
55
|
+
Log.warn msg
|
56
|
+
raise NoResponse, msg
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module SNMP
|
63
|
+
class VarBind
|
64
|
+
def as_ip
|
65
|
+
SNMP::IpAddress.new(value).to_s
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
data/netcrawl.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'netcrawl'
|
3
|
+
s.version = '0.0.1'
|
4
|
+
s.platform = Gem::Platform::RUBY
|
5
|
+
s.authors = [ 'Saku Ytti' ]
|
6
|
+
s.email = %w( saku@ytti.fi )
|
7
|
+
s.homepage = 'http://github.com/ytti/netcrawl'
|
8
|
+
s.summary = 'lldp/cdp crawler'
|
9
|
+
s.description = 'given snmp community and one node crawls through the network to produce list/dot file'
|
10
|
+
s.rubyforge_project = s.name
|
11
|
+
s.files = `git ls-files`.split("\n")
|
12
|
+
s.executables = %w( netcrawl )
|
13
|
+
s.require_path = 'lib'
|
14
|
+
|
15
|
+
s.add_dependency 'snmp'
|
16
|
+
s.add_dependency 'slop'
|
17
|
+
s.add_dependency 'asetus'
|
18
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: netcrawl
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Saku Ytti
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-03-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: snmp
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: slop
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: asetus
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: given snmp community and one node crawls through the network to produce
|
63
|
+
list/dot file
|
64
|
+
email:
|
65
|
+
- saku@ytti.fi
|
66
|
+
executables:
|
67
|
+
- netcrawl
|
68
|
+
extensions: []
|
69
|
+
extra_rdoc_files: []
|
70
|
+
files:
|
71
|
+
- .gitignore
|
72
|
+
- Gemfile
|
73
|
+
- README.md
|
74
|
+
- Rakefile
|
75
|
+
- bin/netcrawl
|
76
|
+
- lib/netcrawl.rb
|
77
|
+
- lib/netcrawl/.cli.rb.swp
|
78
|
+
- lib/netcrawl/cli.rb
|
79
|
+
- lib/netcrawl/config.rb
|
80
|
+
- lib/netcrawl/dns.rb
|
81
|
+
- lib/netcrawl/method/cdp.rb
|
82
|
+
- lib/netcrawl/method/lldp.rb
|
83
|
+
- lib/netcrawl/namemap.rb
|
84
|
+
- lib/netcrawl/output.rb
|
85
|
+
- lib/netcrawl/output/dot.rb
|
86
|
+
- lib/netcrawl/pollmap.rb
|
87
|
+
- lib/netcrawl/snmp.rb
|
88
|
+
- netcrawl.gemspec
|
89
|
+
homepage: http://github.com/ytti/netcrawl
|
90
|
+
licenses: []
|
91
|
+
post_install_message:
|
92
|
+
rdoc_options: []
|
93
|
+
require_paths:
|
94
|
+
- lib
|
95
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
+
none: false
|
97
|
+
requirements:
|
98
|
+
- - '>='
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
segments:
|
102
|
+
- 0
|
103
|
+
hash: -152408900674429923
|
104
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
segments:
|
111
|
+
- 0
|
112
|
+
hash: -152408900674429923
|
113
|
+
requirements: []
|
114
|
+
rubyforge_project: netcrawl
|
115
|
+
rubygems_version: 1.8.25
|
116
|
+
signing_key:
|
117
|
+
specification_version: 3
|
118
|
+
summary: lldp/cdp crawler
|
119
|
+
test_files: []
|