netutils 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +4 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/bin/acl +109 -0
- data/bin/alaxala-deploy +271 -0
- data/bin/config-diff-check +111 -0
- data/bin/config-gets +64 -0
- data/bin/console +14 -0
- data/bin/host-locate-on-demand +102 -0
- data/bin/ipaddr-resolv +74 -0
- data/bin/ipaddr-resolv.sh +97 -0
- data/bin/mac-drop +84 -0
- data/bin/mac-nodrop +45 -0
- data/bin/port-shutdown +78 -0
- data/bin/setup +8 -0
- data/lib/netutils.rb +118 -0
- data/lib/netutils/arp.rb +28 -0
- data/lib/netutils/cli.rb +702 -0
- data/lib/netutils/cli/alaxala.rb +121 -0
- data/lib/netutils/cli/alaxala/interface.rb +137 -0
- data/lib/netutils/cli/alaxala/lldp.rb +166 -0
- data/lib/netutils/cli/alaxala/macfib.rb +62 -0
- data/lib/netutils/cli/alaxala/showarp.rb +51 -0
- data/lib/netutils/cli/alaxala/showroute.rb +86 -0
- data/lib/netutils/cli/alaxala/showvrf.rb +46 -0
- data/lib/netutils/cli/aruba.rb +15 -0
- data/lib/netutils/cli/cisco.rb +45 -0
- data/lib/netutils/cli/cisco/cdp.rb +117 -0
- data/lib/netutils/cli/cisco/ifsummary.rb +32 -0
- data/lib/netutils/cli/cisco/interface.rb +67 -0
- data/lib/netutils/cli/cisco/macfib.rb +38 -0
- data/lib/netutils/cli/cisco/showarp.rb +27 -0
- data/lib/netutils/cli/cisco/showinterface.rb +27 -0
- data/lib/netutils/cli/cisco/showroute.rb +73 -0
- data/lib/netutils/cli/cisco/showvrf.rb +45 -0
- data/lib/netutils/cli/nec.rb +20 -0
- data/lib/netutils/cli/nec/lldp.rb +16 -0
- data/lib/netutils/cli/paloalto.rb +21 -0
- data/lib/netutils/fsm.rb +43 -0
- data/lib/netutils/macaddr.rb +51 -0
- data/lib/netutils/oncequeue.rb +78 -0
- data/lib/netutils/parser.rb +30 -0
- data/lib/netutils/rib.rb +80 -0
- data/lib/netutils/switch.rb +402 -0
- data/lib/netutils/tunnel.rb +8 -0
- data/lib/netutils/version.rb +3 -0
- data/lib/netutils/vrf.rb +42 -0
- data/log/.gitignore +1 -0
- data/netutils.gemspec +33 -0
- metadata +195 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'netutils/fsm'
|
2
|
+
|
3
|
+
class Parser < FSM
|
4
|
+
def initialize
|
5
|
+
super
|
6
|
+
@regexp = Array.new
|
7
|
+
end
|
8
|
+
|
9
|
+
def add(sname, cb, regexp = nil)
|
10
|
+
super(sname, cb)
|
11
|
+
@regexp.push(regexp)
|
12
|
+
end
|
13
|
+
|
14
|
+
def regexp
|
15
|
+
r = @regexp[@state]
|
16
|
+
r = /^.*$/ if r == nil
|
17
|
+
return r
|
18
|
+
end
|
19
|
+
|
20
|
+
def parse(buf)
|
21
|
+
buf.each_line do |l|
|
22
|
+
unless l.chomp! =~ regexp
|
23
|
+
raise(ArgumentError,
|
24
|
+
"No match at #{state_name}: \"#{l}\" " +
|
25
|
+
"to #{regexp.to_s}")
|
26
|
+
end
|
27
|
+
send(cb, l, $~)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/netutils/rib.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
class RIB
|
4
|
+
class Route
|
5
|
+
attr_reader :proto, :dst, :prefixlen, :nh, :interface
|
6
|
+
|
7
|
+
def initialize(proto, dst, nh, interface)
|
8
|
+
dst = ip_address_normalize(dst)
|
9
|
+
@proto = proto
|
10
|
+
@dst = dst
|
11
|
+
@prefixlen = prefixlen_get(dst)
|
12
|
+
@nh = nh
|
13
|
+
@interface = interface
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefixlen_get(dst)
|
17
|
+
return dst.split('/')[1].to_i
|
18
|
+
end
|
19
|
+
private :prefixlen_get
|
20
|
+
|
21
|
+
def ip_address_normalize(dst)
|
22
|
+
# XXX IPv4 dependent...
|
23
|
+
n = dst.count('.')
|
24
|
+
case n
|
25
|
+
when 0, 1, 2
|
26
|
+
ia = dst.split('/')[0]
|
27
|
+
for i in 1..3 - n do
|
28
|
+
ia += '.0'
|
29
|
+
end
|
30
|
+
dst = "#{ia}/#{prefixlen_get(dst)}"
|
31
|
+
when 3
|
32
|
+
else
|
33
|
+
raise(ArgumentError,
|
34
|
+
"Invalid IP address: #{dst}")
|
35
|
+
end
|
36
|
+
return dst
|
37
|
+
end
|
38
|
+
private :ip_address_normalize
|
39
|
+
|
40
|
+
def tunnel?
|
41
|
+
@interface =~ /^[tT]unnel/
|
42
|
+
end
|
43
|
+
|
44
|
+
def compare(other)
|
45
|
+
return self if ! other
|
46
|
+
if defined?(PREFERRED_NEXTHOPS)
|
47
|
+
PREFERRED_NEXTHOPS.each do |prefix|
|
48
|
+
ir = IPAddr.new(prefix)
|
49
|
+
if ir.include?(@nh) &&
|
50
|
+
! ir.include?(other.nh)
|
51
|
+
return self
|
52
|
+
elsif ! ir.include?(@nh) &&
|
53
|
+
ir.include?(other.nh)
|
54
|
+
return other
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
return other if @prefixlen < other.prefixlen
|
59
|
+
return self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def initialize
|
64
|
+
@rib = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def add(proto, dst, nh, interface)
|
68
|
+
@rib.push(Route.new(proto, dst, nh, interface))
|
69
|
+
end
|
70
|
+
|
71
|
+
def get(dst, proto = nil)
|
72
|
+
m = []
|
73
|
+
@rib.each do |r|
|
74
|
+
next if proto && r.proto != proto
|
75
|
+
m.push(r) if IPAddr.new(r.dst).include?(dst)
|
76
|
+
end
|
77
|
+
return nil if m.empty?
|
78
|
+
return m
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,402 @@
|
|
1
|
+
require 'netutils/cli'
|
2
|
+
require 'netutils/oncequeue'
|
3
|
+
|
4
|
+
class Switch
|
5
|
+
module Type
|
6
|
+
ROUTER = 1
|
7
|
+
SWITCH = 2
|
8
|
+
BRIDGE = 3
|
9
|
+
HOST = 4
|
10
|
+
UNKNOWN = 5
|
11
|
+
end
|
12
|
+
|
13
|
+
class PortName
|
14
|
+
MAX_ARGS = 3
|
15
|
+
|
16
|
+
attr_reader :numbers
|
17
|
+
|
18
|
+
def initialize(name)
|
19
|
+
if name =~ /^([^0-9]+)([0-9]?.*)/
|
20
|
+
@type = $1.strip
|
21
|
+
@numbers = $2.split('/')
|
22
|
+
else
|
23
|
+
@type = name.strip
|
24
|
+
@numbers = Array.new
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@type + ' ' + @numbers.join('/')
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_csv
|
33
|
+
n = @numbers.dup
|
34
|
+
while n.length < MAX_ARGS do
|
35
|
+
n.unshift('-')
|
36
|
+
end
|
37
|
+
[ @type, n ].join(',')
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Peer
|
42
|
+
attr_reader :sw, :port
|
43
|
+
|
44
|
+
def initialize(sw, portname)
|
45
|
+
@sw = sw
|
46
|
+
@port = portname
|
47
|
+
end
|
48
|
+
|
49
|
+
def has_backlink?
|
50
|
+
return @sw.ports.exists?(@port)
|
51
|
+
end
|
52
|
+
|
53
|
+
def _to_ascii(sep)
|
54
|
+
return "#{@sw.name}#{sep}#{@port}"
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_s
|
58
|
+
_to_ascii(' ')
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_csv
|
62
|
+
_to_ascii(',')
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Peers < Array
|
67
|
+
def add(sw, portname)
|
68
|
+
push(Peer.new(sw, portname))
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class Port
|
73
|
+
attr_reader :name, :peers
|
74
|
+
attr_accessor :up
|
75
|
+
|
76
|
+
def initialize(name, model, type, speed, duplex)
|
77
|
+
@name = PortName.new(name)
|
78
|
+
@model = model
|
79
|
+
@type = type_name(type)
|
80
|
+
@speed = speed
|
81
|
+
@duplex = duplex
|
82
|
+
@peers = Peers.new
|
83
|
+
@up = false
|
84
|
+
end
|
85
|
+
|
86
|
+
def type_name(type)
|
87
|
+
type = $1 if type =~ %r{10/100/(1000BaseT).?}
|
88
|
+
type = $1 if type =~ %r{10/(100BaseTX)}
|
89
|
+
type = '1000BaseT' if type =~ %r{10/100/1000-T.?}
|
90
|
+
type = $1 if type =~ /^(.*) SFP/
|
91
|
+
type = 'none' if type =~ /^Not? .*/
|
92
|
+
return type
|
93
|
+
end
|
94
|
+
|
95
|
+
def dump(p)
|
96
|
+
up = @up? '*' : ' '
|
97
|
+
s = sprintf "#{p}#{up}#{@name.to_s} #{@model} #{@type} "
|
98
|
+
print s
|
99
|
+
indent = 0
|
100
|
+
@peers.each { |peer|
|
101
|
+
l = peer.has_backlink? ? '<->' : '->'
|
102
|
+
printf "% *s#{l} #{peer.to_s}\n", indent, ''
|
103
|
+
indent = s.length
|
104
|
+
}
|
105
|
+
puts '' if @peers.length == 0
|
106
|
+
end
|
107
|
+
|
108
|
+
def dump_csv(p)
|
109
|
+
up = @up? '*' : ' '
|
110
|
+
printf "#{p},#{up},#{@name.to_csv},#{@type}"
|
111
|
+
@peers.each { |peer|
|
112
|
+
printf ",#{peer.to_csv}"
|
113
|
+
}
|
114
|
+
puts ''
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
class Ports
|
119
|
+
def initialize
|
120
|
+
@hash = Hash.new
|
121
|
+
@list = Array.new
|
122
|
+
end
|
123
|
+
|
124
|
+
def add(name, model, type, speed, duplex)
|
125
|
+
port = Port.new(name, model, type, speed, duplex)
|
126
|
+
@list << @hash[port.name.to_s] = port
|
127
|
+
end
|
128
|
+
|
129
|
+
def key(name)
|
130
|
+
PortName.new(name).to_s
|
131
|
+
end
|
132
|
+
|
133
|
+
def [](name)
|
134
|
+
return @hash[key(name)]
|
135
|
+
end
|
136
|
+
|
137
|
+
def exists?(name)
|
138
|
+
return @hash.key?(key(name))
|
139
|
+
end
|
140
|
+
|
141
|
+
def length
|
142
|
+
return @list.length
|
143
|
+
end
|
144
|
+
|
145
|
+
def each
|
146
|
+
@list.each { |p| yield p }
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
attr_reader :name, :type, :ports, :ia
|
151
|
+
attr_accessor :platform, :firmware, :time
|
152
|
+
|
153
|
+
@@retrieve_all = false
|
154
|
+
@@db = Hash.new
|
155
|
+
@@unretrieved = OnceQueue.new
|
156
|
+
@@warn = Array.new
|
157
|
+
|
158
|
+
def initialize(name, type, ia, retrieve = true)
|
159
|
+
name_set(name)
|
160
|
+
@type = type
|
161
|
+
@ports = Ports.new
|
162
|
+
@retrieve = retrieve
|
163
|
+
@cli = nil
|
164
|
+
ip_address_set(ia)
|
165
|
+
|
166
|
+
return self
|
167
|
+
end
|
168
|
+
|
169
|
+
def name_set(name)
|
170
|
+
raise "Already name is set: #{@name}" if @name
|
171
|
+
@name = name
|
172
|
+
@@db[name] = self if name
|
173
|
+
end
|
174
|
+
|
175
|
+
def ip_address_set(ia)
|
176
|
+
return if ! ia
|
177
|
+
return if @ia
|
178
|
+
@ia = ia
|
179
|
+
case @type
|
180
|
+
when Type::ROUTER, Type::SWITCH
|
181
|
+
@@unretrieved.enqueue(self) if @retrieve
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def login
|
186
|
+
return if @cli
|
187
|
+
@cli = CLI.new(@name, @ia)
|
188
|
+
@cli.login
|
189
|
+
name_set(@cli.name) if ! @name
|
190
|
+
#
|
191
|
+
# retrieve interfaces because many commands require
|
192
|
+
# interface name.
|
193
|
+
#
|
194
|
+
interface_gets
|
195
|
+
end
|
196
|
+
|
197
|
+
def logout
|
198
|
+
return if ! @cli
|
199
|
+
@cli.logout
|
200
|
+
@cli = nil
|
201
|
+
end
|
202
|
+
|
203
|
+
def cmd(*argv)
|
204
|
+
@cli.cmd(*argv)
|
205
|
+
end
|
206
|
+
|
207
|
+
def configure
|
208
|
+
@cli.configure
|
209
|
+
end
|
210
|
+
|
211
|
+
def unconfigure
|
212
|
+
@cli.unconfigure
|
213
|
+
end
|
214
|
+
|
215
|
+
def exec(name, *argv)
|
216
|
+
@cli.send(name, *argv)
|
217
|
+
end
|
218
|
+
private :exec
|
219
|
+
|
220
|
+
def maker
|
221
|
+
@cli.maker
|
222
|
+
end
|
223
|
+
|
224
|
+
def maker_to_s
|
225
|
+
@cli.maker_to_s
|
226
|
+
end
|
227
|
+
|
228
|
+
def product
|
229
|
+
@cli.product
|
230
|
+
end
|
231
|
+
|
232
|
+
def prompt
|
233
|
+
@cli.prompt
|
234
|
+
end
|
235
|
+
|
236
|
+
def syslog(*arg)
|
237
|
+
exec(__method__, *arg)
|
238
|
+
end
|
239
|
+
|
240
|
+
def route_gets(ia)
|
241
|
+
exec(__method__, ia)
|
242
|
+
end
|
243
|
+
|
244
|
+
def vrf_gets
|
245
|
+
exec(__method__)
|
246
|
+
end
|
247
|
+
|
248
|
+
def arp_resolve(ia, vrf)
|
249
|
+
exec(__method__, ia, vrf)
|
250
|
+
end
|
251
|
+
|
252
|
+
def mac_address_table_get(ma, vlan)
|
253
|
+
exec(__method__, self, ma, vlan)
|
254
|
+
end
|
255
|
+
|
256
|
+
def interface_gets
|
257
|
+
raise "ERROR: cannot get interfaces twice" if @interface_got
|
258
|
+
@interface_got = true
|
259
|
+
exec(__method__, self)
|
260
|
+
end
|
261
|
+
|
262
|
+
def interface_name(name)
|
263
|
+
exec(__method__, self, name)
|
264
|
+
end
|
265
|
+
|
266
|
+
def interface_name_cli(name)
|
267
|
+
exec(__method__, name)
|
268
|
+
end
|
269
|
+
|
270
|
+
def interface_shutdown(port)
|
271
|
+
exec(__method__, port)
|
272
|
+
end
|
273
|
+
|
274
|
+
def interface_noshutdown(port)
|
275
|
+
exec(__method__, port)
|
276
|
+
end
|
277
|
+
|
278
|
+
def acl_exists?(*arg)
|
279
|
+
exec(__method__, *arg)
|
280
|
+
end
|
281
|
+
|
282
|
+
def acl_add(*arg)
|
283
|
+
exec(__method__, *arg)
|
284
|
+
end
|
285
|
+
|
286
|
+
def acl_delete(*arg)
|
287
|
+
exec(__method__, *arg)
|
288
|
+
end
|
289
|
+
|
290
|
+
def neighbor_gets(*arg)
|
291
|
+
exec(__method__, self, *arg)
|
292
|
+
end
|
293
|
+
|
294
|
+
def macaddr_resolve(ia)
|
295
|
+
vrf_gets.each do |name, vrf|
|
296
|
+
arp = arp_resolve(ia, vrf)
|
297
|
+
next if ! arp
|
298
|
+
#
|
299
|
+
# bark here if static ARP entry is found because
|
300
|
+
# static ARP entry may be for this system itself.
|
301
|
+
#
|
302
|
+
if arp.static
|
303
|
+
raise "ERROR: Static MAC address " +
|
304
|
+
"found for #{ia}"
|
305
|
+
end
|
306
|
+
return arp.ma, vrf, arp.interface
|
307
|
+
end
|
308
|
+
raise "No MAC address found for #{ia}"
|
309
|
+
end
|
310
|
+
|
311
|
+
def self.get(name, type, platform = nil, firmware = nil, time = nil,
|
312
|
+
ia = nil)
|
313
|
+
# XXX should lock
|
314
|
+
if @@db.key?(name)
|
315
|
+
sw = @@db[name]
|
316
|
+
sw.platform = platform if sw.platform == nil
|
317
|
+
sw.firmware = firmware if sw.firmware == nil
|
318
|
+
sw.time = time if sw.time == nil
|
319
|
+
sw.ip_address_set(ia)
|
320
|
+
else
|
321
|
+
sw = Switch.new(name, type, ia, @@retrieve_all)
|
322
|
+
end
|
323
|
+
return sw
|
324
|
+
end
|
325
|
+
|
326
|
+
def self.retrieve
|
327
|
+
raise(ArgumentError, 'no user defined') if USERS.empty?
|
328
|
+
raise(ArgumentError, 'no password defined') if PASSWORDS.empty?
|
329
|
+
if ENABLES.empty?
|
330
|
+
raise(ArgumentError, 'no enable password defined')
|
331
|
+
end
|
332
|
+
|
333
|
+
thread_concurrency = 64
|
334
|
+
for i in 1..thread_concurrency do
|
335
|
+
Thread.new do
|
336
|
+
while true
|
337
|
+
sw = @@unretrieved.dequeue
|
338
|
+
begin
|
339
|
+
yield sw
|
340
|
+
sw.neighbor_gets
|
341
|
+
rescue Errno::ECONNREFUSED,
|
342
|
+
Errno::ECONNRESET,
|
343
|
+
Errno::EPERM,
|
344
|
+
Timeout::Error => e
|
345
|
+
msg = "WARNING: cannot " +
|
346
|
+
"retrieve #{sw.name} " +
|
347
|
+
"(#{sw.ia}): #{e}"
|
348
|
+
puts msg
|
349
|
+
@@warn.push(msg)
|
350
|
+
@@unretrieved.error
|
351
|
+
rescue => e
|
352
|
+
msg = "WARNING: #{sw.name} " +
|
353
|
+
"(#{sw.ia}): #{e}"
|
354
|
+
puts msg
|
355
|
+
@@warn.push(msg)
|
356
|
+
ensure
|
357
|
+
@@unretrieved.done(sw)
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|
361
|
+
end
|
362
|
+
@@unretrieved.wait_all
|
363
|
+
puts 'Finished!!'
|
364
|
+
puts "Total: #{@@unretrieved.total}, Error: #{@@unretrieved.errors}"
|
365
|
+
end
|
366
|
+
|
367
|
+
def self.set_retrieve_all
|
368
|
+
@@retrieve_all = true
|
369
|
+
end
|
370
|
+
|
371
|
+
def each_peer
|
372
|
+
@ports.each { |p| p.peers.each { |peer| yield peer } }
|
373
|
+
end
|
374
|
+
|
375
|
+
def if_dump
|
376
|
+
puts "#{@name}: #{@platform}, #{@time}, #{@ia.to_s}"
|
377
|
+
@ports.each { |p| p.dump(' ') }
|
378
|
+
end
|
379
|
+
|
380
|
+
def if_dump_csv
|
381
|
+
@ports.each { |p| p.dump_csv(@ia.to_s + ',' + @name) }
|
382
|
+
end
|
383
|
+
|
384
|
+
def config_get
|
385
|
+
@config = @cli.config_get
|
386
|
+
end
|
387
|
+
|
388
|
+
def config_dump(dir = 'conf')
|
389
|
+
begin
|
390
|
+
Dir.mkdir(dir)
|
391
|
+
rescue Errno::EEXIST
|
392
|
+
end
|
393
|
+
path = "#{dir}/#{@name}-#{@ia}.conf"
|
394
|
+
f = File.open(path, 'w')
|
395
|
+
f.puts @config
|
396
|
+
f.close
|
397
|
+
end
|
398
|
+
|
399
|
+
def self.warn
|
400
|
+
@@warn.each { |w| puts w }
|
401
|
+
end
|
402
|
+
end
|