hotwired 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ class CLI
5
+ require "slop"
6
+ require_relative "../hotwired"
7
+
8
+ class NoConfig < HotwiredError; end
9
+
10
+ MAX_DELETE = 1
11
+
12
+ # 脚本调度函数入口
13
+ def run
14
+ # 检查 CLI 是否携带相关参数
15
+ if @opts[:poll]
16
+ Log.debug "Start hotwired with specify #{@opts[:poll]}"
17
+ Hotwired.new(cidr: @opts[:poll]).run
18
+ elsif @opts[:remove]
19
+ Log.debug "Start hotwired remove job"
20
+ remove_records @opts[:remove]
21
+ elsif @opts["purge-old"]
22
+ Log.debug "Start hotwired purge-old job"
23
+ remove_old @opts["purge-old"]
24
+ else
25
+ # 缺省的执行方式
26
+ Log.debug "Start hotwired with default params!"
27
+ Hotwired.new.run
28
+ end
29
+ end
30
+
31
+ # 类对象实例化入口函数
32
+ def initialize
33
+ args, @opts = opts_parse
34
+ @arg = args.shift
35
+ CFG.debug = true if @opts[:debug]
36
+ raise NoConfig, "edit ~/.config/hotwired/config" if CONFIG.create
37
+ end
38
+
39
+ # 解析命令行脚本接收参数
40
+ def opts_parse
41
+ opts = Slop.parse do |o|
42
+ # banner "Usage: hotwired [options] [argument]"
43
+ o.on "-h", "--help", "show usage" do
44
+ puts o
45
+ exit
46
+ end
47
+ o.bool "-d", "--debug", "Debugging on"
48
+ o.string "-p", "--poll", "Poll CIDR [argument]"
49
+ o.bool "-r", "--remove", "Remove [argument] from DB"
50
+ o.string "-m", "--max-delete", "Maximum number to delete, default #{MAX_DELETE}"
51
+ o.bool "-o", "--purge-old", "Remove records order than [argument] days"
52
+ o.bool "-s", "--simulate", "Simulate, do not change DB"
53
+ end
54
+ [opts.arguments, opts]
55
+ end
56
+
57
+ # 删除表记录
58
+ def remove_records(name)
59
+ Log.warn "Remove record #{name}"
60
+ DB.new
61
+ delete_records DB::Device.filter(Sequel.like(:ptr, "%#{name}%")).all
62
+ end
63
+
64
+ # 删除历史数据 【 N*天之前 】
65
+ def remove_old(days)
66
+ Log.warn "Remove #{days} days ago data"
67
+ old = (Time.now.utc - days.to_i * 24 * 60 * 60)
68
+ DB.new
69
+ delete_records DB::Device.filter { :last_seen < old }.all
70
+ end
71
+
72
+ # 删除某些主机数据:接收数组对象
73
+ def delete_records(devs)
74
+ Log.debug "delete_records #{devs.size}"
75
+ max_del = @opts["max-delete"] ? @opts["max-delete"] : MAX_DELETE
76
+ # 判断当前表已有数据条目和期望删除条目是否匹配
77
+ if devs.size > max_del.to_i
78
+ puts "Too many matching devices:"
79
+ devs.each do |dev|
80
+ puts " %s (%s)" % [dev.ptr, dev.ip]
81
+ end
82
+ puts "Be more specific"
83
+ else
84
+ puts "Deleting records:"
85
+ devs.each do |dev|
86
+ puts " %s (%s)" % [dev.ptr, dev.ip]
87
+ dev.delete unless @opts[:simulate]
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ require "strada"
5
+ require "fileutils"
6
+
7
+ class Config
8
+ ROOT = File.join ENV["HOME"], ".config", "hotwired"
9
+ CRASH = File.join ROOT, "crash"
10
+ end
11
+
12
+ FileUtils.mkdir_p Config::ROOT
13
+ CONFIG = Strada.new name: "hotwired", load: "false", key_to_s: true
14
+
15
+ CONFIG.default.community = "cisco"
16
+ CONFIG.default.db = File.join Config::ROOT, "hotwired.db"
17
+ CONFIG.default.poll = %w( 192.168.8.0/24 )
18
+ CONFIG.default.ignore = %w( 192.168.8.100 )
19
+ CONFIG.default.mgmt = %w( lo0.0 loopback0 vlan2 )
20
+ CONFIG.default.threads = 50
21
+ CONFIG.default.timeout = 0.25
22
+ CONFIG.default.retries = 2
23
+ CONFIG.default.log = File.join Config::ROOT, "log"
24
+ CONFIG.default.debug = true
25
+
26
+ # 加载初始化配置
27
+ CONFIG.load
28
+ CFG = CONFIG.cfg
29
+
30
+ # 初始化项目日志参数
31
+ Log.file = CFG.log if CFG.log
32
+ Log.level = CFG.debug ? Logger::INFO : Logger::DEBUG
33
+ end
@@ -0,0 +1,237 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 加载项目依赖
4
+ require_relative "log"
5
+ require_relative "config"
6
+ require_relative "snmp"
7
+ require_relative "db/db"
8
+ require_relative "model"
9
+
10
+ # 加载外部依赖
11
+ require "ipaddr"
12
+ require "resolv"
13
+
14
+ module Hotwired
15
+ # 类方法属性
16
+ class << self
17
+ # 实例化 Core 对象
18
+ def new(opts = {})
19
+ Core.new opts
20
+ end
21
+
22
+ # 轮询 host 数据
23
+ def poll(opts = {})
24
+ host = opts.delete :host
25
+ raise HotwiredError, "'host' not given" unless host
26
+ hotwire = new(opts)
27
+ result = hotwire.poll Resolv.getaddress(host)
28
+ # 数据转储
29
+ hotwire.make_record result if result
30
+ end
31
+ end
32
+
33
+ # 类对象方法属性
34
+ class Core
35
+ # 类对象初始化函数入口
36
+ def initialize(opts = {})
37
+ @opts = opts
38
+ @community = opts.delete(:community) || CFG.community
39
+ end
40
+
41
+ # 类对象外部调用函数入口
42
+ def run
43
+ # 解析变量
44
+ cidr = @opts.delete :cidr
45
+ # @output = @opts.delete :output
46
+
47
+ # 设置缺省 logger 输出
48
+ # unless @output
49
+ # @output = Logger.new $stdout
50
+ # @output.formatter = proc { |_, _, _, msg| "#{msg}\n" }
51
+ # end
52
+
53
+ # 初始化变量及遍历 CIDR
54
+ poll, ignores = resolve_networks cidr
55
+ # 实例化线程和数据库联结
56
+ @mutex = Mutex.new
57
+ @db = DB.new
58
+ threads = []
59
+ # 线程遇到异常及时终止
60
+ Thread.abort_on_exception = true
61
+
62
+ # 遍历待轮询的 IPAddr
63
+ poll.each do |net|
64
+ net.to_range.each do |ip|
65
+ # 检查当前地址是否需要被忽略
66
+ next if ignores.any? { |ignore| ignore.include? ip }
67
+ # 清除空闲线程
68
+ while threads.size >= CFG.threads
69
+ threads.delete_if { |thread| not thread.alive? }
70
+ sleep 0.01
71
+ end
72
+ # 线程不够则主动添加线程
73
+ threads << Thread.new do
74
+ result = poll ip
75
+ @mutex.synchronize { process result } if result
76
+ end
77
+ end
78
+ end
79
+ # 激活线程,开始干活
80
+ threads.each { |thread| thread.join }
81
+ end
82
+
83
+ # 轮询单个 IP 设备信息
84
+ def poll(ip)
85
+ result = nil
86
+ # 实例化 SNMP 对象,批量获取相关监控数据
87
+ snmp = SNMP.new(ip.to_s, @community)
88
+ oids = snmp.dbget
89
+
90
+ if oids
91
+ # 早期异常拦截
92
+ Log.debug "SNMP::NoSuchObject #{ip}" if oids[:sysDescr] == ::SNMP::NoSuchObject
93
+ return nil if oids[:sysDescr] == ::SNMP::NoSuchObject
94
+ # 初始化变量,并尝试刷新接口描述信息
95
+ result = { oids: oids, ip: ip, int: "n/a" }
96
+ # 联机查询数据
97
+ index = snmp.ip2index(ip.to_s)
98
+ int = snmp.ifdescr(index)
99
+ # 逻辑处理
100
+ if index
101
+ if int
102
+ result[:int] = int.downcase
103
+ else
104
+ Log.debug "no ifDescr for #{index} at #{ip}"
105
+ end
106
+ else
107
+ Log.debug "no ifIndex for #{ip}"
108
+ end
109
+ end
110
+ # 关闭 SNMP 会话并返回结果
111
+ snmp.close
112
+ result
113
+ end
114
+
115
+ # 新增表记录
116
+ def make_record(opt)
117
+ {
118
+ ip: opt[:ip].to_s,
119
+ ptr: ip2name(opt[:ip].to_s),
120
+ model: Model.map(opt[:oids][:sysDescr], opt[:oids][:sysObjectID]),
121
+ oid_ifDescr: opt[:int],
122
+ oid_sysName: opt[:oids][:sysName],
123
+ oid_sysLocation: opt[:oids][:sysLocation],
124
+ oid_sysDescr: opt[:oids][:sysDescr],
125
+ oid_sysObjectID: opt[:oids][:sysObjectID].join("."),
126
+ }
127
+ end
128
+
129
+ private
130
+ def process(opt)
131
+ opt = normalize_opt opt
132
+ record = make_record opt
133
+ # 查询表中已有数据
134
+ old_by_ip, old_by_sysname = @db.old(record[:ip], record[:oid_sysName])
135
+
136
+ # unique box having non-unique sysname
137
+ # old_by_sysname = false if record[:oid_sysDescr].match 'Application Control Engine'
138
+
139
+ # 查无记录则需要新增
140
+ if (not old_by_sysname) && (not old_by_ip)
141
+ # all new device
142
+ @output.info "ptr [%s] sysName [%s] ip [%s]" % [record[:ptr], record[:oid_sysName], record[:ip]]
143
+ Log.info "#{record[:ip]} added"
144
+ @db.add record
145
+ # 根据 IP 可以查询到数据,但设备名称发生变化
146
+ elsif (not old_by_sysname) && old_by_ip
147
+ # IP seen, name not, device got renamed?
148
+ Log.info "#{record[:ip]} got renamed"
149
+ @db.update record, [:ip, old_by_ip[:ip]]
150
+ # 根据设备名称可以查询到数据,但 IP 地址发生变化
151
+ elsif old_by_sysname && (not old_by_ip)
152
+ # name exists, but IP is new, figure out if we wan to use old or new IP
153
+ decide_old_new record, old_by_sysname
154
+ # 已有记录刷新即可
155
+ elsif old_by_sysname && old_by_ip
156
+ both_seen record, old_by_sysname, old_by_ip
157
+ end
158
+ end
159
+
160
+ # 根据 IP 和设备名称均可检索到数据,需要进一步判断查询出来的记录是否完全一致
161
+ def both_seen(record, old_by_sysname, old_by_ip)
162
+ if old_by_sysname == old_by_ip
163
+ # no changes, updating same record
164
+ Log.debug "#{record[:ip]} refreshed, no channges"
165
+ @db.update record, [:oid_sysName, old_by_sysname[:oid_sysName]]
166
+ else
167
+ # same name seen and same IP seen, but records were not same (device got renumbered to existing node + existing node got delete?)
168
+ Log.warn "#{record[:ip]}, unique entries for IP and sysName in DB, updating by IP"
169
+ @db.update record, [:ip, old_by_ip[:ip]]
170
+ end
171
+ end
172
+
173
+ # 设备新旧记录优先级校验
174
+ # 新数据比旧数据优先级更高,可以理解更新可信?
175
+ def decide_old_new(record, old_by_sysname)
176
+ new_int_pref = (CFG.mgmt.index(record[:oid_ifDescr]) or 100)
177
+ old_int_pref = (CFG.mgmt.index(old_by_sysname[:oid_ifDescr]) or 99)
178
+
179
+ if new_int_pref < old_int_pref
180
+ # 原有数据优先级更高
181
+ # new int is more preferable than old
182
+ Log.info "#{record[:ip]} is replacing inferior #{old_by_sysname[:ip]}"
183
+ @db.update record, [:oid_sysName, old_by_sysname[:oid_sysName]]
184
+ elsif (new_int_pref == 100) && (old_int_pref == 99)
185
+ # 新老数据接口均未标注为管理口
186
+ # neither old or new interface is known good MGMT interface
187
+ if SNMP.new(old_by_sysname[:ip], @community).sysdescr
188
+ # 如果老IP可以检索数据,则无需更新
189
+ # if old IP works, don't update
190
+ Log.debug "#{record[:ip]} not updating, previously seen as #{old_by_sysname[:ip]}"
191
+ else
192
+ Log.info "#{record[:ip]} updating, old #{old_by_sysname[:ip]} is dead"
193
+ @db.update record, [:oid_sysName, old_by_sysname[:oid_sysName]]
194
+ end
195
+ elsif new_int_pref >= old_int_pref
196
+ # 新数据优先级高于老数据,则无需进一步处理
197
+ # nothing to do, we have better entry
198
+ Log.debug "#{record[:ip]} already seen as superior via #{old_by_sysname[:ip]}"
199
+ else
200
+ Log.error "not updating, new: #{record[:ip]}, old: #{old_by_sysname[:ip]}"
201
+ end
202
+ end
203
+
204
+ # 序列化 opt 参数
205
+ def normalize_opt(opt)
206
+ opt[:oids][:sysName].sub!(/-re[1-9]\./, "-re0.")
207
+ opt
208
+ end
209
+
210
+ # 解析 IP 关联的主机名
211
+ def ip2name(ip)
212
+ Resolv.getname ip rescue ip
213
+ end
214
+
215
+ # 解析 cidr
216
+ def resolve_networks(cidr)
217
+ # 如未接收外部变量则使用缺省值
218
+ cidr = cidr ? [cidr].flatten : CFG.poll
219
+
220
+ # 从 CIDR 中剔除排除清单
221
+ # 支持数组以及文本形式,数据返回包含2个数组对象的数组
222
+ [cidr, CFG.ignore].map do |nets|
223
+ if nets.respond_to? :each
224
+ nets.map { |net| IPAddr.new net }
225
+ else
226
+ out = []
227
+ File.read(nets).each_line do |net|
228
+ # 模糊的 IP 地址正则表达式
229
+ net = net.match(/^([\d.\/]+)$/)
230
+ out << IPAddr.new(net[1]) if net
231
+ end
232
+ out
233
+ end
234
+ end
235
+ end
236
+ end
237
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ class DB
5
+ require "sequel"
6
+ require "sqlite3"
7
+
8
+ # 类对象初始化函数入口
9
+ def initialize
10
+ Log.debug "Initialize Hotwired object ... "
11
+ @db = Sequel.sqlite(CFG.db, max_connections: 1, pool_timeout: 60)
12
+ create_table # unless @db.table_exists?(:devices)
13
+ require_relative "model"
14
+ end
15
+
16
+ # 新建数据库记录
17
+ def add(record)
18
+ # 添加时间戳属性
19
+ record[:first_seen] = record[:last_seen] = Time.now.utc
20
+ record[:active] = true
21
+ Log.debug "adding: #{record}"
22
+ Device.new(record).save
23
+ end
24
+
25
+ # 更新数据
26
+ def update(record, where)
27
+ # 更新数据入库时间
28
+ record[:last_seen] = Time.now.utc
29
+ record[:active] = true
30
+ Log.debug "updating (where: #{where}): #{record}"
31
+ Device[where.first.to_sym => where.last].update(record)
32
+ end
33
+
34
+ # 查询数据表记录信息
35
+ def old(ip, oid_sysname)
36
+ ip = Device[ip: ip]
37
+ sysname = Device[oid_sysName: oid_sysname]
38
+ [ip, sysname]
39
+ end
40
+
41
+ private
42
+ def create_table
43
+ # 检查是否存在数据表结构,不存在则新建
44
+ @db.create_table? :devices do
45
+ primary_key :id
46
+ String :ip
47
+ String :ptr
48
+ String :model
49
+ String :oid_ifDescr
50
+ Boolean :active
51
+ Time :first_seen
52
+ Time :last_seen
53
+ String :oid_sysName
54
+ String :oid_sysLocation
55
+ String :oid_sysDescr
56
+ String :oid_sysObjectID
57
+ end
58
+ Log.debug "Creating SQLITE DB"
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ class DB
5
+ class Device < Sequel::Model
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ require "logger"
5
+
6
+ class Logger < Logger
7
+ def initialize(target = STDOUT)
8
+ super target
9
+ self.level = Logger::DEBUG
10
+ end
11
+
12
+ def file=(target)
13
+ @log_device = LogDevice.new target
14
+ end
15
+ end
16
+
17
+ Log = Logger.new
18
+ end
@@ -0,0 +1,103 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ class Model
5
+ def self.map(sysDescr, sysObjectID)
6
+ case sysDescr
7
+ when /Cisco Catalyst Operating System/i
8
+ "catos"
9
+ when /Cisco Controller/
10
+ "aireos"
11
+ when /IOS XR/
12
+ "iosxr"
13
+ when /NX-OS/
14
+ "nxos"
15
+ when /JUNOS/
16
+ "junos"
17
+ when /Arista Networks EOS/
18
+ "eos"
19
+ when /IronWare/
20
+ "ironware"
21
+ when /TiMOS/
22
+ "timos"
23
+ when /ExtremeXOS/
24
+ "xos"
25
+ when /Cisco Adaptive Security Appliance/
26
+ "asa"
27
+ when /Brocade Fibre Channel Switch/
28
+ "fabricos"
29
+ when /Brocade VDX/
30
+ "nos"
31
+ when /cisco/i, /Application Control Engine/i
32
+ "ios"
33
+ when /Force10 OS/
34
+ "ftos"
35
+ when /Versatile Routing Platform/
36
+ "vrp"
37
+ when /^NetScreen/, /^SSG-\d+/
38
+ "screenos"
39
+ when /^Summit/
40
+ "xos"
41
+ when /^Alcatel-Lucent \S+ [789]\./ # aos <7 is vxworks, >=7 is linux
42
+ "aos7"
43
+ when /^AOS-W/
44
+ "aosw"
45
+ when /^Alcatel-Lucent/
46
+ "aos"
47
+ when /\s+ACOS\s+/
48
+ "acos"
49
+ when /ProCurve/ # ProCurve OS does not seem to have name?
50
+ "procurve"
51
+ when /ASAM/
52
+ "isam"
53
+ when /^\d+[A-Z]\sEthernet Switch$/
54
+ "powerconnect"
55
+ when /Ericsson IPOS/
56
+ "ssr"
57
+ when /Huawei Integrated Access Software/
58
+ "hias"
59
+ else
60
+ case sysObjectID
61
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.12356."))
62
+ "fortios" # 1.3.6.1.4.1.12356.101.1.10004
63
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.6486."))
64
+ "aos" # 1.3.6.1.4.1.6486.800.1.1.2.1.11.2.2
65
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.6027."))
66
+ "ftos" # 1.3.6.1.4.1.6027.1.3.4
67
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.1588."))
68
+ "fabricos " # 1.3.6.1.4.1.1588.2.1.1.1
69
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.3224."))
70
+ "screenos" # 1.3.6.1.4.1.3224.1.51 (SSG) 1.16 (Netscreen 2k)
71
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.674."))
72
+ "powerconnect" # 1.3.6.1.4.1.674.10895.3031
73
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.22610."))
74
+ "acos" # 1.3.6.1.4.1.22610.1.3.14
75
+ when Regexp.new("^" + Regexp.quote(".1.3.6.1.4.1.637."))
76
+ "isam" # 1.3.6.1.4.1.637.61.1
77
+ when Regexp.new("^" + Regexp.quote(".1.3.6.1.4.1.2011."))
78
+ "vrp" # 1.3.6.1.4.1.2011.2.224.67 (AR1220F)
79
+ when Regexp.new("^" + Regexp.quote(".1.3.6.1.4.1.1588."))
80
+ "nos" # 1.3.6.1.4.1.1588.2.2.1.1.1.5 (VDX)
81
+ when Regexp.new("^" + Regexp.quote(".1.3.6.1.4.1.1916."))
82
+ "xos" # 1.3.6.1.4.1.1916.2.76 (X450a-48t)
83
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.9.1.745"))
84
+ "asa" # 1.3.6.1.4.1.9.1.745
85
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.20858.2.600"))
86
+ "casa" # 1.3.6.1.4.1.20858.2.600
87
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.2011.2.169"))
88
+ "hias" # 1.3.6.1.4.1.2011.2.169
89
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.2011.2.300"))
90
+ "hias" # 1.3.6.1.4.1.2011.2.300 (MA5800 OLT)
91
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.2352.1"))
92
+ "ssr" # 1.3.6.1.4.1.2352.1.17 and .18
93
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.193.218.1"))
94
+ "ssr" # 1.3.6.1.4.1.193.218.1.17 and .18
95
+ when Regexp.new("^" + Regexp.quote("1.3.6.1.4.1.11.2.3.7.11"))
96
+ "procurve" # Aruba switches are really HP Procurve
97
+ else
98
+ "unsupported"
99
+ end
100
+ end
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ class SNMP
5
+ require "snmp"
6
+
7
+ class InvalidResponse < StandardError; end
8
+
9
+ # 需要遍历 oid 写入数据库的相关属性
10
+ DB_OID = {
11
+ sysDescr: "1.3.6.1.2.1.1.1.0", # 运行系统描述
12
+ sysObjectID: "1.3.6.1.2.1.1.2.0", # 厂商识别
13
+ sysName: "1.3.6.1.2.1.1.5.0", # 系统名称
14
+ sysLocation: "1.3.6.1.2.1.1.6.0", # 系统位置
15
+ }
16
+
17
+ OID = {
18
+ ifDescr: "1.3.6.1.2.1.2.2.1.2", # 接口描述
19
+ ipCidrRouteIfIndex: "1.3.6.1.2.1.4.24.4.1.5", # addr.255.255.255.255.0.0.0.0.0
20
+ ipAdEntIfIndex: "1.3.6.1.2.1.4.20.1.2", # addr | 旧格式
21
+ ipAddressIfIndex: "1.3.6.1.2.1.4.34.1.3", # 1,2 (uni,any) . 4,16 (size) . addr | 新格式
22
+ }
23
+ UNICAST = 1
24
+ IPV4 = 4
25
+ BULK_MAX = 30
26
+
27
+ # 类对象初始化函数入口
28
+ def initialize(host, community = CFG.community)
29
+ @snmp = ::SNMP::Manager.new(Host: host, Community: community, Timeout: CFG.timeout, Retries: CFG.retries, MibModules: [])
30
+ end
31
+
32
+ # 关闭会话
33
+ def close
34
+ @snmp.close
35
+ end
36
+
37
+ # 查询 oid 监控数据
38
+ # 支持多个片段拼接成一个完整的 OID 字串
39
+ def get(*oid)
40
+ oid = [oid].flatten.join(".")
41
+ begin
42
+ @snmp.get(oid).each_varbind { |vb| return vb }
43
+ rescue ::SNMP::RequestTimeout, Errno::EACCES
44
+ false
45
+ end
46
+ end
47
+
48
+ # 批量查询 oid,入参为 HASH 数据结构
49
+ def mget(oids = DB_OID)
50
+ result = {}
51
+ begin
52
+ res = @snmp.get(oids.map { |_, oid| oid })
53
+ # 抛出异常
54
+ raise InvalidResponse, "#{res.error_status} from #{@snmp.config[:host]}" unless res.error_status == :noError
55
+ res.each_varbind do |vb|
56
+ oids.each do |key, oid|
57
+ if vb.name.to_str == oid
58
+ result[key] = vb.value
59
+ # 找到即跳出当前循环体
60
+ next
61
+ end
62
+ end
63
+ end
64
+ rescue ::SNMP::RequestTimeout, Errno::EACCES
65
+ return false
66
+ rescue InvalidResponse
67
+ return false
68
+ end
69
+ result
70
+ end
71
+
72
+ # 方法别名
73
+ alias dbget mget
74
+
75
+ # 遍历根节点检索数据
76
+ def bulkwalk(root)
77
+ # 初始化遍历
78
+ last, oid, vbs = false, root, []
79
+ # 轮询 root oid 主体逻辑
80
+ until last
81
+ r = @snmp.get_bulk 0, BULK_MAX, oid
82
+ r.varbind_list.each do |vb|
83
+ oid = vb.name.to_str
84
+ # 跳出循环条件
85
+ (last = true; break) unless oid.match?(/^#{Regexp.quote root}/)
86
+ vbs << vb
87
+ end
88
+ end
89
+ vbs
90
+ end
91
+
92
+ # 查询设备描述
93
+ def sysdescr
94
+ get DB_OID[:sysDescr]
95
+ end
96
+
97
+ # IP 转索引
98
+ def ip2index(ip)
99
+ # 根据 IP 查询其路由及接口IP索引序号
100
+ oids = mget(route: [OID[:ipCidrRouteIfIndex], ip, "255.255.255.255.0.0.0.0.0"].join("."),
101
+ new: [OID[:ipAddressIfIndex], UNICAST, IPV4, ip].join("."),
102
+ old: [OID[:ipAdEntIfIndex], ip].join("."))
103
+ return false unless oids
104
+ # 优先使用 route 命中,如查询不到则进一步尝试使用其他属性
105
+ index = oids[:route]
106
+ index = oids[:new] if (not index.class == ::SNMP::Integer) || (index.to_s == "0")
107
+ index = oids[:old] if (not index.class == ::SNMP::Integer) || (index.to_s == "0")
108
+ return false unless index.class == ::SNMP::Integer
109
+ index.to_s
110
+ end
111
+
112
+ # 查询接口描述
113
+ def ifdescr(index)
114
+ descr = get OID[:ifDescr], index
115
+ return false unless descr && (descr.value.class == ::SNMP::OctetString)
116
+ descr.value.to_s
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ VERSION = "0.0.1"
5
+ end
data/lib/hotwired.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Hotwired
4
+ class HotwiredError < StandardError; end
5
+
6
+ require_relative "hotwired/core"
7
+ end
data/sig/hotwired.rbs ADDED
@@ -0,0 +1,4 @@
1
+ module Hotwired
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end