netutils 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|