netdisco 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +117 -0
- data/Rakefile +8 -0
- data/bin/netdisco +10 -0
- data/lib/netdisco/cli.rb +70 -0
- data/lib/netdisco/config.rb +42 -0
- data/lib/netdisco/dns.rb +54 -0
- data/lib/netdisco/method/cdp.rb +36 -0
- data/lib/netdisco/method/lldp.rb +53 -0
- data/lib/netdisco/method/xdp.rb +50 -0
- data/lib/netdisco/name_map.rb +14 -0
- data/lib/netdisco/output/dot.rb +75 -0
- data/lib/netdisco/output.rb +94 -0
- data/lib/netdisco/peer.rb +40 -0
- data/lib/netdisco/poll_map.rb +19 -0
- data/lib/netdisco/snmp.rb +155 -0
- data/lib/netdisco/version.rb +5 -0
- data/lib/netdisco.rb +69 -0
- metadata +109 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a0ce0d6214b2a7b6c2b0619f00c82a84656ad9c1b784336305f9bde8587fbfc8
|
4
|
+
data.tar.gz: f3d6b2d3ae31913f42f24ff065146a737da60cf38e36d2d6064ccb59e3e90aaf
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fd1ae67b1b81632bf30e4166c1eb45c890352bf10e56bd14c1e6983943c09b3fff5449dba27ea2f23e7296477b25236720f1ecb7d2070792aa2e0aaea4437717
|
7
|
+
data.tar.gz: 71c515cfdc3ff4d03167053df28d64364381416f38e73273d7565292deb34ad670df87dbfc8a15378f94fa942c0d6a7b5b819fdd52cc3ce8f92e4c4bc2591cf9
|
data/CHANGELOG.md
ADDED
data/CODE_OF_CONDUCT.md
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# Contributor Covenant Code of Conduct
|
2
|
+
|
3
|
+
## Our Pledge
|
4
|
+
|
5
|
+
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
|
6
|
+
|
7
|
+
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
|
8
|
+
|
9
|
+
## Our Standards
|
10
|
+
|
11
|
+
Examples of behavior that contributes to a positive environment for our community include:
|
12
|
+
|
13
|
+
* Demonstrating empathy and kindness toward other people
|
14
|
+
* Being respectful of differing opinions, viewpoints, and experiences
|
15
|
+
* Giving and gracefully accepting constructive feedback
|
16
|
+
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
17
|
+
* Focusing on what is best not just for us as individuals, but for the overall community
|
18
|
+
|
19
|
+
Examples of unacceptable behavior include:
|
20
|
+
|
21
|
+
* The use of sexualized language or imagery, and sexual attention or
|
22
|
+
advances of any kind
|
23
|
+
* Trolling, insulting or derogatory comments, and personal or political attacks
|
24
|
+
* Public or private harassment
|
25
|
+
* Publishing others' private information, such as a physical or email
|
26
|
+
address, without their explicit permission
|
27
|
+
* Other conduct which could reasonably be considered inappropriate in a
|
28
|
+
professional setting
|
29
|
+
|
30
|
+
## Enforcement Responsibilities
|
31
|
+
|
32
|
+
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
|
33
|
+
|
34
|
+
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
|
35
|
+
|
36
|
+
## Scope
|
37
|
+
|
38
|
+
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
|
39
|
+
|
40
|
+
## Enforcement
|
41
|
+
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at careline@foxmail.com. All complaints will be reviewed and investigated promptly and fairly.
|
43
|
+
|
44
|
+
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
|
45
|
+
|
46
|
+
## Enforcement Guidelines
|
47
|
+
|
48
|
+
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
|
49
|
+
|
50
|
+
### 1. Correction
|
51
|
+
|
52
|
+
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
53
|
+
|
54
|
+
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
55
|
+
|
56
|
+
### 2. Warning
|
57
|
+
|
58
|
+
**Community Impact**: A violation through a single incident or series of actions.
|
59
|
+
|
60
|
+
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
61
|
+
|
62
|
+
### 3. Temporary Ban
|
63
|
+
|
64
|
+
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
|
65
|
+
|
66
|
+
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
67
|
+
|
68
|
+
### 4. Permanent Ban
|
69
|
+
|
70
|
+
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
71
|
+
|
72
|
+
**Consequence**: A permanent ban from any sort of public interaction within the community.
|
73
|
+
|
74
|
+
## Attribution
|
75
|
+
|
76
|
+
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
|
77
|
+
available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
78
|
+
|
79
|
+
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
|
80
|
+
|
81
|
+
[homepage]: https://www.contributor-covenant.org
|
82
|
+
|
83
|
+
For answers to common questions about this code of conduct, see the FAQ at
|
84
|
+
https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 WENWU.YAN
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
# netdisco
|
2
|
+
|
3
|
+
Given snmp community and single device, discover the network via discovering
|
4
|
+
LLDP/CDP neighbours, while producing list or dot file (for graphviz digraphs)
|
5
|
+
|
6
|
+
## Install
|
7
|
+
|
8
|
+
% gem install netdisco
|
9
|
+
|
10
|
+
## Use
|
11
|
+
|
12
|
+
% netdisco --graphiz router.example.com
|
13
|
+
|
14
|
+
## Command line
|
15
|
+
|
16
|
+
```shell
|
17
|
+
Usage: netdisco [options] hostname
|
18
|
+
-g, --graphviz dot output use 'dot -Tpng -o map.png map.dot'
|
19
|
+
-l, --list list nodes
|
20
|
+
-j, --json json output
|
21
|
+
-y, --yaml yaml output
|
22
|
+
-a, --hash hash/associative array output
|
23
|
+
-r, --resolve resolve addresses to names
|
24
|
+
-p, --purge remove peers not in configured CIDR
|
25
|
+
-c, --community SNMP community to use
|
26
|
+
-d, --debug turn debugging on
|
27
|
+
-h, --help Display this help message.
|
28
|
+
|
29
|
+
```
|
30
|
+
|
31
|
+
* graphiz - graphis (dot) output
|
32
|
+
* list - list nodes found
|
33
|
+
* json - json output
|
34
|
+
* yaml - yaml output
|
35
|
+
* hash - ruby hash output
|
36
|
+
* resolve - resolve IP addresses
|
37
|
+
* purge - remove non-cidr matching peers from output
|
38
|
+
* community - sets snmp community
|
39
|
+
* debug - turn on debugging
|
40
|
+
|
41
|
+
## Config
|
42
|
+
|
43
|
+
```yaml
|
44
|
+
---
|
45
|
+
use:
|
46
|
+
- LLDP
|
47
|
+
- CDP
|
48
|
+
poll:
|
49
|
+
- 192.0.2.0/24
|
50
|
+
snmp:
|
51
|
+
community: public
|
52
|
+
timeout: 1
|
53
|
+
retries: 2
|
54
|
+
bulkrows: 35
|
55
|
+
dot:
|
56
|
+
bothlinks: true
|
57
|
+
color:
|
58
|
+
- - cpe
|
59
|
+
- gold
|
60
|
+
- - -sw
|
61
|
+
- blue
|
62
|
+
- - -pe
|
63
|
+
- red
|
64
|
+
- - ' -p'
|
65
|
+
- yellow
|
66
|
+
dns:
|
67
|
+
afi:
|
68
|
+
log: STDERR
|
69
|
+
debug: false
|
70
|
+
namemap:
|
71
|
+
- - -re\d+
|
72
|
+
- ''
|
73
|
+
- - (.*(?<!as23456.net)$)
|
74
|
+
- \1.as23456.net
|
75
|
+
```
|
76
|
+
|
77
|
+
* use - methods to use for crawling
|
78
|
+
* poll - cidrs to allow snmp for
|
79
|
+
* snmp community - snmp community to use
|
80
|
+
* snmp timout - snmp timout in seconds
|
81
|
+
* snmp retries - snmp retries count
|
82
|
+
* snmp bulkrows - snmp row count for bulkget
|
83
|
+
* dot bothlinks - show a-b and b-a link
|
84
|
+
* dot color - regexp to color, first hit used
|
85
|
+
* dns afi - ipv4/ipv6 or nil
|
86
|
+
* log - STDERR/STDOUT or file
|
87
|
+
* debug - debugging
|
88
|
+
* namemap - map (LLDP) name to FQDN (JunOS does not give domain)
|
89
|
+
|
90
|
+
## Library use
|
91
|
+
|
92
|
+
require 'netdisco'
|
93
|
+
output = netdisco.new.discover('192.0.2.1').to_hash
|
94
|
+
|
95
|
+
## Development
|
96
|
+
|
97
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive
|
98
|
+
prompt that will allow you to experiment.
|
99
|
+
|
100
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
101
|
+
version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
|
102
|
+
push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
103
|
+
|
104
|
+
## Contributing
|
105
|
+
|
106
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/netdisco. This project is intended
|
107
|
+
to be a safe, welcoming space for collaboration, and contributors are expected to adhere to
|
108
|
+
the [code of conduct](https://github.com/ciscolive/netdisco-ruby/blob/main/CODE_OF_CONDUCT.md).
|
109
|
+
|
110
|
+
## License
|
111
|
+
|
112
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
113
|
+
|
114
|
+
## Code of Conduct
|
115
|
+
|
116
|
+
Everyone interacting in the Netdisco project's codebases, issue trackers, chat rooms and mailing lists is expected to
|
117
|
+
follow the [code of conduct](https://github.com/ciscolive/netdisco-ruby/blob/main/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
data/bin/netdisco
ADDED
data/lib/netdisco/cli.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../netdisco"
|
4
|
+
require "slop"
|
5
|
+
|
6
|
+
class Netdisco
|
7
|
+
class CLI
|
8
|
+
class MissingHost < NetdiscoError; end
|
9
|
+
|
10
|
+
class NoConfig < NetdiscoError; end
|
11
|
+
|
12
|
+
# 网络邻居发现入口函数
|
13
|
+
def run
|
14
|
+
output = Netdisco.new.discover @host
|
15
|
+
|
16
|
+
# 邻居关系发现后修正处理逻辑钩子函数
|
17
|
+
output.clean! if @opts[:purge]
|
18
|
+
output.resolve! if @opts[:resolve]
|
19
|
+
# 将计算结果转换格式打印
|
20
|
+
if @opts[:graphviz]
|
21
|
+
output.to_dot
|
22
|
+
elsif @opts[:list]
|
23
|
+
output.to_a
|
24
|
+
elsif @opts[:json]
|
25
|
+
output.to_json
|
26
|
+
elsif @opts[:yaml]
|
27
|
+
output.to_yaml
|
28
|
+
else
|
29
|
+
output.to_hash
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# 类对象初始化入口函数
|
34
|
+
def initialize
|
35
|
+
raise NoConfig, "edit ~/.config/netdisco/config" if CONFIG.create
|
36
|
+
args, @opts = opt_parse
|
37
|
+
@host = DNS.getip args.shift
|
38
|
+
raise MissingHost, "no hostname given as argument" unless @host
|
39
|
+
|
40
|
+
CFG.snmp.community = @opts[:community] if @opts[:community]
|
41
|
+
CFG.debug = true if @opts[:debug]
|
42
|
+
CFG.ipname = true if @opts[:ipname]
|
43
|
+
end
|
44
|
+
|
45
|
+
# CLI 命令行交互解析
|
46
|
+
def opt_parse
|
47
|
+
opts = Slop.parse do |o|
|
48
|
+
o.on "-h", "--help", "show usage" do
|
49
|
+
puts o
|
50
|
+
exit
|
51
|
+
end
|
52
|
+
o.on "-g", "--graphviz", "dot output use 'dot -Tpng -o map.png map.dot'"
|
53
|
+
o.on "-l", "--list", "list nodes"
|
54
|
+
o.on "-j", "--json", "json output"
|
55
|
+
o.on "-y", "--yaml", "yaml output"
|
56
|
+
o.on "-a", "--hash", "hash/associative array output"
|
57
|
+
o.on "-r", "--resolve", "resolve addresses to names"
|
58
|
+
o.on "-p", "--purge", "remove peers not in configured CIDR"
|
59
|
+
o.string "-c=", "--community", "SNMP community to use"
|
60
|
+
o.bool "-d", "--debug", "turn debugging on"
|
61
|
+
o.bool "-i", "--ipname", "use rev(ip) name instead of discovered name"
|
62
|
+
o.on "-v", "--version", "print the version" do
|
63
|
+
puts Slop::VERSION
|
64
|
+
exit
|
65
|
+
end
|
66
|
+
end
|
67
|
+
[opts.arguments, opts]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "strada"
|
4
|
+
require "logger"
|
5
|
+
|
6
|
+
class Netdisco
|
7
|
+
# 实例化配置对象
|
8
|
+
CONFIG = Strada.new name: "netdisco", load: false
|
9
|
+
|
10
|
+
# 设置默认参数
|
11
|
+
CONFIG.default.use = %w(LLDP CDP)
|
12
|
+
CONFIG.default.poll = %w[192.168.8.0/24]
|
13
|
+
CONFIG.default.snmp.community = "cisco"
|
14
|
+
CONFIG.default.snmp.timeout = 15
|
15
|
+
CONFIG.default.snmp.retries = 1
|
16
|
+
CONFIG.default.snmp.bulkrows = 30 # 1500B packet should fit about 50 :cdpCacheAddress rows
|
17
|
+
CONFIG.default.dot.bothlinks = false # keep both a-b and b-a links
|
18
|
+
CONFIG.default.dot.linklabels = true # label link with interface names
|
19
|
+
CONFIG.default.dot.color = [# regexp of host => color
|
20
|
+
%w[cpe gold],
|
21
|
+
%w[-sw blue],
|
22
|
+
%w[-pe red],
|
23
|
+
%w[-p yellow],
|
24
|
+
]
|
25
|
+
CONFIG.default.dns.afi = nil # could be 'ipv4' or 'ipv6'
|
26
|
+
CONFIG.default.log = "STDERR"
|
27
|
+
CONFIG.default.debug = true
|
28
|
+
CONFIG.default.name_map = [# regexp match+sub of hostname (needed for LLDP)
|
29
|
+
['-re\d+', ""],
|
30
|
+
["^KILLME(.*(?<!mojo.local)$)", '\1.mojo.local'], # adds missing domain name
|
31
|
+
]
|
32
|
+
# 加载配置并设置对应常量
|
33
|
+
CONFIG.load
|
34
|
+
CFG = CONFIG.cfg
|
35
|
+
# 设置项目日志参数
|
36
|
+
log = CFG.log
|
37
|
+
log = STDERR if log == "STDERR"
|
38
|
+
log = STDOUT if log == "STDOUT"
|
39
|
+
Log = Logger.new log
|
40
|
+
Log.level = Logger::INFO
|
41
|
+
Log.level = Logger::DEBUG if CFG.debug
|
42
|
+
end
|
data/lib/netdisco/dns.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "resolv"
|
4
|
+
|
5
|
+
class Netdisco
|
6
|
+
class Resolve
|
7
|
+
# 对象实例化入口函数
|
8
|
+
def initialize
|
9
|
+
@cache_ip = {}
|
10
|
+
@cache_name = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
# @param [String] name DNS name which we try to resolve to IP
|
14
|
+
# @return [String, nil] string if name resolves to IP, otherwise nil
|
15
|
+
# 优先加载缓存,不能存在则实时采集
|
16
|
+
def getip(name)
|
17
|
+
if @cache_ip.has_key? name
|
18
|
+
@cache_ip[name]
|
19
|
+
else
|
20
|
+
# 可能一个 name 同时存在 IPV4 和 IPV6 解析
|
21
|
+
begin
|
22
|
+
if CFG.dns.afi == "ipv4"
|
23
|
+
@cache_ip[name] = Resolv::DNS.new.getresource(name, Resolv::DNS::Resource::IN::A).address
|
24
|
+
elsif CFG.dns.afi == "ipv6"
|
25
|
+
@cache_ip[name] = Resolv::DNS.new.getresource(name, Resolv::DNS::Resource::IN::AAAA).address
|
26
|
+
else
|
27
|
+
@cache_ip[name] = Resolv.getaddress name
|
28
|
+
end
|
29
|
+
rescue => error
|
30
|
+
Log.debug "DNS resolution for '#{name}' raised error '#{error.class}' with message '#{error.message}'"
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# @param [String] ip DNS IP which we try to resolve to name
|
37
|
+
# @return [String] name if it resolves, ip otherwise
|
38
|
+
# 优先加载缓存,不能存在则实时采集
|
39
|
+
def getname(ip)
|
40
|
+
if @cache_name.has_key? ip
|
41
|
+
@cache_name[ip]
|
42
|
+
else
|
43
|
+
begin
|
44
|
+
@cache_name[ip] = Resolv.getname ip
|
45
|
+
rescue => error
|
46
|
+
Log.debug "DNS resolution for '#{ip}' raised error '#{error.class}' with message '#{error.message}'"
|
47
|
+
@cache_name[ip] = ip
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
DNS = Resolve.new
|
54
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Netdisco
|
4
|
+
class CDP < XDP
|
5
|
+
MIB = "1.3.6.1.4.1.9.9.23" # ciscoCdpMIB
|
6
|
+
|
7
|
+
OID = {
|
8
|
+
# 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
|
9
|
+
# cdpInterfaceName: "1.3.6.1.2.1.31.1.1.1.18",
|
10
|
+
cdpInterfaceName: "1.3.6.1.4.1.9.9.23.1.1.1.1.6",
|
11
|
+
cdpCacheAddress: "1.3.6.1.4.1.9.9.23.1.2.1.1.4",
|
12
|
+
cdpCacheDeviceId: "1.3.6.1.4.1.9.9.23.1.2.1.1.6",
|
13
|
+
cdpCacheDevicePort: "1.3.6.1.4.1.9.9.23.1.2.1.1.7",
|
14
|
+
}
|
15
|
+
PEERS_BY = OID[:cdpCacheDeviceId]
|
16
|
+
|
17
|
+
private
|
18
|
+
def make_peers
|
19
|
+
peers = []
|
20
|
+
@mib.by_oid(PEERS_BY).each do |_, vb|
|
21
|
+
peer = Peer.new
|
22
|
+
peer_id = vb.oid_id(PEERS_BY)
|
23
|
+
peer.oid = get_oid_hash(peer_id)
|
24
|
+
peer.raw_ip = @mib[OID[:cdpCacheAddress], peer_id].as_ip
|
25
|
+
peer.raw_name = @mib[OID[:cdpCacheDeviceId], peer_id].value
|
26
|
+
peer.ip = get_ip(peer.raw_ip, peer.raw_name)
|
27
|
+
peer.dst = @mib[OID[:cdpCacheDevicePort], peer_id].value
|
28
|
+
peer.src = @mib[OID[:cdpInterfaceName], peer_id.first]
|
29
|
+
peer.src = peer.src&.value if peer.src
|
30
|
+
peer.raw_ip = @mib[OID[:cdpCacheAddress], peer_id].value
|
31
|
+
peers << peer
|
32
|
+
end
|
33
|
+
peers
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Netdisco
|
4
|
+
class LLDP < XDP
|
5
|
+
MIB = "1.0.8802.1.1.2" # lldpMIB
|
6
|
+
OID = {
|
7
|
+
# http://standards.ieee.org/getieee802/download/802.1AB-2009.pdf
|
8
|
+
# finding IP address for LLDP neighbour as of JunOS 13.3R1 and IOS 15.0(2)SG8 is not practical
|
9
|
+
# ifsubtype is ifindex but value 0 for JunOS neighbours
|
10
|
+
# ifsubtype is systemportnumber for IOS neighbours (what ever that is)
|
11
|
+
# luckily some IP address is in the OID key itself, while dodgy, better than nothing
|
12
|
+
# in JunOS it was some random RFC1918 address in VRF interface, not something I could poll
|
13
|
+
# .1.0.8802.1.1.2.1.4.2.1.3.0.134.10.1.4.10.0.0.4
|
14
|
+
# in IOS it was usable address
|
15
|
+
# .1.0.8802.1.1.2.1.4.2.1.3.0.257.1.1.4.62.243.146.245
|
16
|
+
# (1.4 is IPv4)
|
17
|
+
# as well LocPortId/RemPortId is hard, it is 'local' (snmpifindex really) in JunOS, but ifName in IOS
|
18
|
+
lldpLocPortId: "1.0.8802.1.1.2.1.3.7.1.3",
|
19
|
+
lldpRemChassisIdSubtype: "1.0.8802.1.1.2.1.4.1.1.4", # CSCO and JNPR use 4 (MAC address) rendering ChassisID useless
|
20
|
+
lldpRemChassisId: "1.0.8802.1.1.2.1.4.1.1.5",
|
21
|
+
lldpRemPortIdSubtype: "1.0.8802.1.1.2.1.4.1.1.6",
|
22
|
+
lldpRemPortId: "1.0.8802.1.1.2.1.4.1.1.7",
|
23
|
+
lldpRemSysName: "1.0.8802.1.1.2.1.4.1.1.9",
|
24
|
+
lldpRemManAddrIfSubtype: "1.0.8802.1.1.2.1.4.2.1.3",
|
25
|
+
}
|
26
|
+
PEERS_BY = OID[:lldpRemChassisId]
|
27
|
+
PortSubType = {
|
28
|
+
mac_address: 3,
|
29
|
+
}
|
30
|
+
|
31
|
+
private
|
32
|
+
def make_peers
|
33
|
+
peers = []
|
34
|
+
@mib.by_oid(PEERS_BY).each do |_, vb|
|
35
|
+
peer = Peer.new
|
36
|
+
peer_id = vb.oid_id(PEERS_BY)
|
37
|
+
peer.oid = get_oid_hash peer_id
|
38
|
+
ip = @mib.by_partial(OID[:lldpRemManAddrIfSubtype], peer_id)
|
39
|
+
peer.raw_ip = ip.oid[-4..-1].join(".") if ip # FIXME: IPv4 specific
|
40
|
+
peer.raw_ip ||= "192.0.2.255" # sometimes we can't fnd any IP (EX2200 talking Arista found)
|
41
|
+
peer.raw_name = @mib[OID[:lldpRemSysName], peer_id].value
|
42
|
+
peer.ip = get_ip(peer.raw_ip, peer.raw_name)
|
43
|
+
peer.dst = @mib[OID[:lldpRemPortId], peer_id].value
|
44
|
+
if @mib[OID[:lldpRemPortIdSubtype], peer_id].value.to_i == PortSubType[:mac_address]
|
45
|
+
peer.dst = peer.dst&.each_char&.map { |e| "%02x" % e.ord }&.join&.scan(/..../).join(".")
|
46
|
+
end
|
47
|
+
peer.src = @mib[OID[:lldpLocPortId], peer_id[1]].value rescue nil
|
48
|
+
peers << peer
|
49
|
+
end
|
50
|
+
peers
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../snmp"
|
4
|
+
|
5
|
+
class Netdisco
|
6
|
+
class XDP
|
7
|
+
# 类对象方法属性
|
8
|
+
attr_reader :mib
|
9
|
+
|
10
|
+
# 加载模块方法到类对象
|
11
|
+
include NameMap
|
12
|
+
|
13
|
+
# 对象实例化入口函数
|
14
|
+
def initialize(host)
|
15
|
+
@snmp = SNMP.new host
|
16
|
+
end
|
17
|
+
|
18
|
+
# 类属性方法
|
19
|
+
# @param [String] host host to query
|
20
|
+
# @return [Array(Netdisco::Peer)] neighbor information
|
21
|
+
def self.peers(host)
|
22
|
+
new(host).poll
|
23
|
+
end
|
24
|
+
|
25
|
+
# 轮询设备邻居关系
|
26
|
+
def poll
|
27
|
+
@mib = @snmp.hashwalk self.class::MIB
|
28
|
+
# require "pp"; PP.pp @mib
|
29
|
+
make_peers
|
30
|
+
rescue SNMP::NoResponse
|
31
|
+
[]
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def get_ip(ip, name)
|
36
|
+
DNS.getip(name_map(name)) || ip
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_oid_hash(peer_id)
|
40
|
+
oid_hash = {}
|
41
|
+
self.class::OID.each do |name, oid|
|
42
|
+
oid_hash[name] = @mib[oid, peer_id]
|
43
|
+
end
|
44
|
+
oid_hash
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
require_relative "cdp"
|
50
|
+
require_relative "lldp"
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Netdisco
|
4
|
+
class Output
|
5
|
+
class Dot
|
6
|
+
INDENT = " " * 2
|
7
|
+
DEFAULT_COLOR = "black"
|
8
|
+
def self.output(output)
|
9
|
+
new(output).to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
str = "graph Netdisco {\n"
|
14
|
+
@output.to_a.each do |host|
|
15
|
+
host_label = label(host)
|
16
|
+
# 调整图形修饰符
|
17
|
+
str += INDENT + id(host) + "[label=\"#{host_label}\" color=\"#{color(host_label)}\"]\n"
|
18
|
+
|
19
|
+
if @peers.has_key? host
|
20
|
+
@peers[host].each do |peer|
|
21
|
+
peer_name = @resolve ? peer.name : peer.ip
|
22
|
+
next if (not CFG.dot.bothlinks) && @connections.include?([peer_name, host].sort)
|
23
|
+
@connections << [peer_name, host].sort
|
24
|
+
labels = ""
|
25
|
+
labels = "[headlabel=\"#{peer.dst}\" taillabel=\"#{peer.src}\"]" if CFG.dot.linklabel
|
26
|
+
str << INDENT + INDENT + id(host) + " -- " + id(peer_name) + labels + "\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
str += "}\n"
|
31
|
+
str
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def initialize(output)
|
36
|
+
@output = output
|
37
|
+
@connections = []
|
38
|
+
@peers = @output.peers
|
39
|
+
@resolve = @output.resolve
|
40
|
+
end
|
41
|
+
|
42
|
+
def id(host)
|
43
|
+
host = host.gsub(/[-.]/, "_")
|
44
|
+
"_" + host
|
45
|
+
end
|
46
|
+
|
47
|
+
def label(want_host)
|
48
|
+
label = nil
|
49
|
+
return want_host if CFG.ipname == true
|
50
|
+
@peers.each do |host, peers|
|
51
|
+
peers.each do |peer|
|
52
|
+
got_host = @resolve ? peer.name : peer.ip
|
53
|
+
if want_host == got_host
|
54
|
+
label = peer.raw_name
|
55
|
+
break
|
56
|
+
end
|
57
|
+
end
|
58
|
+
break if label
|
59
|
+
end
|
60
|
+
label or want_host
|
61
|
+
end
|
62
|
+
|
63
|
+
def color(host)
|
64
|
+
color = nil
|
65
|
+
CFG.dot.color.each do |re, clr|
|
66
|
+
if host.match re
|
67
|
+
color = clr
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
color or DEFAULT_COLOR
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Netdisco
|
4
|
+
class Output
|
5
|
+
# 类对象属性
|
6
|
+
attr_reader :peers, :resolve, :poll_map
|
7
|
+
|
8
|
+
# 对象初始化入口函数
|
9
|
+
# 将邻居发现清单以及本地的轮询列表清单比对后,格式化输出相关数据
|
10
|
+
def initialize(peers, poll_map)
|
11
|
+
@peers = peers
|
12
|
+
@poll_map = poll_map
|
13
|
+
@resolve = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# 动态方法
|
17
|
+
def method_missing(name, *args)
|
18
|
+
raise NoMethodError, "invalid method #{name} for #{inspect}:#{self.class}" unless name.match?(/to_.*/)
|
19
|
+
output = File.basename name[3..-1]
|
20
|
+
require_relative "output/" + output
|
21
|
+
output = Netdisco::Output.const_get output.capitalize
|
22
|
+
output.send :output, self
|
23
|
+
end
|
24
|
+
|
25
|
+
# 自动更新设备主机名,并更新解析状态
|
26
|
+
# resolves ip addresses of peers and @peers keys
|
27
|
+
# @return [void]
|
28
|
+
def resolve!
|
29
|
+
@resolve = true
|
30
|
+
new_peers = {}
|
31
|
+
@peers.each do |host, peers|
|
32
|
+
peers.each { |peer| peer.name }
|
33
|
+
name = DNS.getname host
|
34
|
+
new_peers[name] = peers
|
35
|
+
end
|
36
|
+
@peers = new_peers
|
37
|
+
end
|
38
|
+
|
39
|
+
# 自动移除非探测名单范围内的设备
|
40
|
+
# remove peers not matching to configured CIDR
|
41
|
+
# @return [void]
|
42
|
+
def clean!
|
43
|
+
@peers.values.each do |peers|
|
44
|
+
peers.delete_if { |peer| not @poll_map.include? peer.ip }
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# @return [Hash] of nodes found
|
49
|
+
def to_h
|
50
|
+
hash = {}
|
51
|
+
@peers.each do |host, peers|
|
52
|
+
array = []
|
53
|
+
peers.each do |peer|
|
54
|
+
array << peer.to_h
|
55
|
+
end
|
56
|
+
hash[host] = array
|
57
|
+
end
|
58
|
+
hash
|
59
|
+
end
|
60
|
+
|
61
|
+
# @return [Array] of nodes found
|
62
|
+
def to_a
|
63
|
+
nodes = []
|
64
|
+
@peers.each do |host, peers|
|
65
|
+
nodes << host
|
66
|
+
peers.each do |peer|
|
67
|
+
nodes << @resolve ? peer.name : peer.ip
|
68
|
+
end
|
69
|
+
end
|
70
|
+
# 将 nodes 展开为属组并去重排序
|
71
|
+
nodes.flatten.uniq
|
72
|
+
end
|
73
|
+
|
74
|
+
# @return [String] pretty print of hash
|
75
|
+
def to_hash
|
76
|
+
require "pp"
|
77
|
+
out = ""
|
78
|
+
PP.pp to_h, out
|
79
|
+
out
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [String] yaml of hosts and peers found
|
83
|
+
def to_yaml
|
84
|
+
require "yaml"
|
85
|
+
YAML.dump to_h
|
86
|
+
end
|
87
|
+
|
88
|
+
# @return [String] json of hosts and peers found
|
89
|
+
def to_json
|
90
|
+
require "json"
|
91
|
+
JSON.pretty_generate to_h
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Netdisco
|
4
|
+
class Peer
|
5
|
+
# 类对象方法属性
|
6
|
+
attr_accessor :ip, :raw_ip, :raw_name, :src, :dst, :oid
|
7
|
+
|
8
|
+
# 初始化函数
|
9
|
+
def initialize
|
10
|
+
@ip = nil # Best guess of system IP
|
11
|
+
@name = nil # Reverse of said IP
|
12
|
+
@raw_ip = nil # IP as seen in polling
|
13
|
+
@raw_name = nil # Name as seen in polling
|
14
|
+
@src = nil # SRC/local interface
|
15
|
+
@dst = nil # DST/remote interface
|
16
|
+
@oid = {} # Hash of oids collected
|
17
|
+
end
|
18
|
+
|
19
|
+
# 设置邻居名称
|
20
|
+
def name
|
21
|
+
@name ||= DNS.getname @ip
|
22
|
+
end
|
23
|
+
|
24
|
+
# 将邻居对象转换为 HASH
|
25
|
+
def to_h
|
26
|
+
{
|
27
|
+
"ip" => ip.to_s,
|
28
|
+
"name" => name.to_s,
|
29
|
+
"interface" => {
|
30
|
+
"source" => src.to_s,
|
31
|
+
"destination" => dst.to_s
|
32
|
+
},
|
33
|
+
"raw" => {
|
34
|
+
"ip" => raw_ip.to_s,
|
35
|
+
"name" => raw_name.to_s
|
36
|
+
}
|
37
|
+
}
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ipaddr"
|
4
|
+
|
5
|
+
class Netdisco
|
6
|
+
class PollMap
|
7
|
+
attr_reader :poll
|
8
|
+
|
9
|
+
# 类对象初始化函数入口
|
10
|
+
def initialize
|
11
|
+
@poll = CFG.poll.map { |cidr| IPAddr.new cidr }
|
12
|
+
end
|
13
|
+
|
14
|
+
# 判断是否包含某个地址
|
15
|
+
def include?(addr)
|
16
|
+
@poll.any? { |cidr| cidr.include? addr }
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
require "snmp"
|
2
|
+
|
3
|
+
class Netdisco
|
4
|
+
class SNMP
|
5
|
+
class NetdiscoError < StandardError; end
|
6
|
+
|
7
|
+
class NoResponse < NetdiscoError; end
|
8
|
+
|
9
|
+
# 类对象初始化入口函数
|
10
|
+
def initialize(host, community = CFG.snmp.community, timeout = CFG.snmp.timeout, retries = CFG.snmp.retries)
|
11
|
+
@host = host
|
12
|
+
@snmp = ::SNMP::Manager.new(Host: @host, Community: community, Timeout: timeout, Retries: retries, MibModules: [])
|
13
|
+
end
|
14
|
+
|
15
|
+
# Closes the SNMP connection
|
16
|
+
# @return [void]
|
17
|
+
def close
|
18
|
+
@snmp.close
|
19
|
+
end
|
20
|
+
|
21
|
+
# 查询某个 oid 监控值
|
22
|
+
# Gets one oid, return value
|
23
|
+
# @param [String] oid to get
|
24
|
+
# @return [SNMP::VarBind]
|
25
|
+
def get(oid)
|
26
|
+
mget([oid]).first
|
27
|
+
end
|
28
|
+
|
29
|
+
# 批量查询多个 oids 监控值
|
30
|
+
# Get multiple oids, return array of values
|
31
|
+
# @param [Array(String)] oids to get
|
32
|
+
# @return [SNMP::VarBindList]
|
33
|
+
def mget(oids)
|
34
|
+
snmp :get, oids
|
35
|
+
end
|
36
|
+
|
37
|
+
# 遍历 root_oid 监控值
|
38
|
+
# Bulkwalk everything below root oid
|
39
|
+
# @param [String] root oid to start from
|
40
|
+
# @return [Array(SNMP::VarBind)]
|
41
|
+
def bulkwalk(root)
|
42
|
+
# 初始化变量
|
43
|
+
last, oid, results = false, root.dup, []
|
44
|
+
# 将点分10进制字串转换为数组对象
|
45
|
+
root = root.split(".").map { |chr| chr.to_i }
|
46
|
+
# 遍历 root_oid
|
47
|
+
until last
|
48
|
+
vbs = snmp(:get_bulk, 0, CFG.snmp.bulkrows, oid).varbind_list
|
49
|
+
vbs.each do |vb|
|
50
|
+
oid = vb.oid
|
51
|
+
# 解析到的 oid 不匹配 root 跳出循环
|
52
|
+
(last = true; break) unless oid[0..root.size - 1] == root
|
53
|
+
results << vb
|
54
|
+
end
|
55
|
+
end
|
56
|
+
results
|
57
|
+
end
|
58
|
+
|
59
|
+
# 将遍历的 root 监控值转换为 VBHash 样式
|
60
|
+
# bulkwalks oid and returns hash with oid as key
|
61
|
+
# @param [String] oid root oid to walk
|
62
|
+
# @yield [VBHash] hash containing oids found
|
63
|
+
# @return [Hash] resulting hash
|
64
|
+
def hashwalk(oid, &block)
|
65
|
+
hash = VBHash.new
|
66
|
+
bulkwalk(oid).each do |vb|
|
67
|
+
# value, key = block.call(vb)
|
68
|
+
# key ||= vb.oid
|
69
|
+
hash[vb.oid] = vb
|
70
|
+
end
|
71
|
+
hash
|
72
|
+
end
|
73
|
+
|
74
|
+
private
|
75
|
+
def snmp(method, *args)
|
76
|
+
@snmp.send method, *args
|
77
|
+
rescue ::SNMP::RequestTimeout, Errno::EACCES => error
|
78
|
+
msg = "host '#{@host}' raised '#{error.class}' with message '#{error.message}' for method '#{method}' with args '#{args}'"
|
79
|
+
Log.warn msg
|
80
|
+
raise NoResponse, msg
|
81
|
+
end
|
82
|
+
|
83
|
+
# Hash with some helper methods to easier work with VarBinds
|
84
|
+
class VBHash < Hash
|
85
|
+
alias :org_bracket :[]
|
86
|
+
undef :[]
|
87
|
+
|
88
|
+
# 精确查询
|
89
|
+
# @param [Array(Strin, Array)] oid root oid under which you want all oids below it
|
90
|
+
# @return [VBHash] oids which start with param oid
|
91
|
+
def by_oid(*oid)
|
92
|
+
oid = arg_to_oid(*oid)
|
93
|
+
hash = select do |key, value|
|
94
|
+
key[0..oid.size - 1] == oid
|
95
|
+
end
|
96
|
+
new_hash = VBHash.new
|
97
|
+
new_hash.merge hash
|
98
|
+
end
|
99
|
+
|
100
|
+
# 模糊匹配
|
101
|
+
# @param [Array(String, Array)] args partial match 3.4.6 would match to 1.2.3.4.6.7.8
|
102
|
+
# @return [SNMP::VarBind, nil] matching element
|
103
|
+
def by_partial(*args)
|
104
|
+
oid = arg_to_oid(*args)
|
105
|
+
# got = nil
|
106
|
+
keys.each do |key|
|
107
|
+
return self[key] if key.each_cons(oid.size).find { |e| e == oid }
|
108
|
+
# if key.each_cons(oid.size).find { |e| e == oid }
|
109
|
+
# got = self[key]
|
110
|
+
# break
|
111
|
+
# end
|
112
|
+
end
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# @param [Array[String, Array)] key which you want, multiple arguments compiled into single key
|
117
|
+
# @return [SNMP::VarBind] matching element
|
118
|
+
def [](*args)
|
119
|
+
org_bracket arg_to_oid(*args)
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
def arg_to_oid(*args)
|
124
|
+
# FIXME && TODO
|
125
|
+
key = []
|
126
|
+
args.each do |arg|
|
127
|
+
if Array === arg
|
128
|
+
key += arg.map { |e| e.to_i }
|
129
|
+
elsif Fixnum === arg
|
130
|
+
key << arg
|
131
|
+
else
|
132
|
+
key += arg.split(".").map { |e| e.to_i }
|
133
|
+
end
|
134
|
+
end
|
135
|
+
key
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
module SNMP
|
142
|
+
class VarBind
|
143
|
+
# @return [String] VarBind value as IP address
|
144
|
+
def as_ip
|
145
|
+
SNMP::IpAddress.new(value).to_s
|
146
|
+
end
|
147
|
+
|
148
|
+
# @param [String] root oid which is removed from self.oid
|
149
|
+
# @return [Array] oid remaining after specified root oid
|
150
|
+
def oid_id(root)
|
151
|
+
root = root.split(".").map { |e| e.to_i }
|
152
|
+
oid[root.size..-1]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
data/lib/netdisco.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# 加载项目依赖
|
4
|
+
require_relative "netdisco/config"
|
5
|
+
require_relative "netdisco/poll_map"
|
6
|
+
require_relative "netdisco/name_map"
|
7
|
+
require_relative "netdisco/peer"
|
8
|
+
require_relative "netdisco/method/xdp"
|
9
|
+
require_relative "netdisco/dns"
|
10
|
+
require_relative "netdisco/output"
|
11
|
+
|
12
|
+
class Netdisco
|
13
|
+
class NetdiscoError < StandardError; end
|
14
|
+
|
15
|
+
class MethodNotFound < NetdiscoError; end
|
16
|
+
|
17
|
+
# 类对象方法属性
|
18
|
+
attr_reader :hosts
|
19
|
+
|
20
|
+
# 类对象初始化函数
|
21
|
+
def initialize
|
22
|
+
@methods = []
|
23
|
+
@hosts = {}
|
24
|
+
@poll = PollMap.new
|
25
|
+
|
26
|
+
# 动态加载邻居发现协议
|
27
|
+
CFG.use.each do |method|
|
28
|
+
method = File.basename method.to_s
|
29
|
+
file = "netdisco/method/#{method.downcase}"
|
30
|
+
require_relative file
|
31
|
+
@methods << Netdisco.const_get(method)
|
32
|
+
rescue NameError, LoadError
|
33
|
+
raise MethodNotFound, "unable to find method '#{method}'"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [String] host host to start discover from
|
38
|
+
# @return [Netdisco::Output]
|
39
|
+
def discover(host)
|
40
|
+
recurse host
|
41
|
+
Output.new @hosts, @poll
|
42
|
+
end
|
43
|
+
|
44
|
+
# @param [String] host host to get list of peers from
|
45
|
+
# @return [Array] list of peers seen connected to host
|
46
|
+
def neighbors(host)
|
47
|
+
peers = []
|
48
|
+
# 不同协议可能发现相同的联结关系,处理完成后根据 ip 自动去重
|
49
|
+
@methods.each do |method|
|
50
|
+
peers << method.send(:peers, host)
|
51
|
+
end
|
52
|
+
peers.flatten.uniq { |peer| peer.ip }
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given string of IP address, recurses through peers seen and populates @hosts hash
|
56
|
+
# @param [String] host host to start recurse from
|
57
|
+
# @return [void]
|
58
|
+
def recurse(host)
|
59
|
+
# 第一步获取设备的邻居关系,同时将邻居关系缓存下来
|
60
|
+
@hosts[host] = peers = neighbors(host)
|
61
|
+
# 第二步遍历邻居的邻居关系
|
62
|
+
require "pp"; PP.pp @hosts[host]
|
63
|
+
peers.each do |peer|
|
64
|
+
next if @hosts.has_key? peer.ip
|
65
|
+
next unless @poll.include? peer.ip
|
66
|
+
discover peer.ip
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: netdisco
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- WENWU.YAN
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-04-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: snmp
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: slop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: strada
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 0.0.2
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.2
|
55
|
+
description: Netdisco is working for network snmp discovery
|
56
|
+
email:
|
57
|
+
- careline@foxmail.com
|
58
|
+
executables:
|
59
|
+
- netdisco
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- CHANGELOG.md
|
64
|
+
- CODE_OF_CONDUCT.md
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- bin/netdisco
|
69
|
+
- lib/netdisco.rb
|
70
|
+
- lib/netdisco/cli.rb
|
71
|
+
- lib/netdisco/config.rb
|
72
|
+
- lib/netdisco/dns.rb
|
73
|
+
- lib/netdisco/method/cdp.rb
|
74
|
+
- lib/netdisco/method/lldp.rb
|
75
|
+
- lib/netdisco/method/xdp.rb
|
76
|
+
- lib/netdisco/name_map.rb
|
77
|
+
- lib/netdisco/output.rb
|
78
|
+
- lib/netdisco/output/dot.rb
|
79
|
+
- lib/netdisco/peer.rb
|
80
|
+
- lib/netdisco/poll_map.rb
|
81
|
+
- lib/netdisco/snmp.rb
|
82
|
+
- lib/netdisco/version.rb
|
83
|
+
homepage: https://github.com/ciscolive/netdisco-ruby
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata:
|
87
|
+
homepage_uri: https://github.com/ciscolive/netdisco-ruby
|
88
|
+
source_code_uri: https://github.com/ciscolive/zabbix-rails
|
89
|
+
changelog_uri: https://github.com/ciscolive/zabbix-rails/blob/main/README.md
|
90
|
+
post_install_message:
|
91
|
+
rdoc_options: []
|
92
|
+
require_paths:
|
93
|
+
- lib
|
94
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: 2.6.0
|
99
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
requirements: []
|
105
|
+
rubygems_version: 3.3.3
|
106
|
+
signing_key:
|
107
|
+
specification_version: 4
|
108
|
+
summary: Netdisco is working for network snmp discovery
|
109
|
+
test_files: []
|