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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +4 -0
  7. data/README.md +36 -0
  8. data/Rakefile +6 -0
  9. data/bin/acl +109 -0
  10. data/bin/alaxala-deploy +271 -0
  11. data/bin/config-diff-check +111 -0
  12. data/bin/config-gets +64 -0
  13. data/bin/console +14 -0
  14. data/bin/host-locate-on-demand +102 -0
  15. data/bin/ipaddr-resolv +74 -0
  16. data/bin/ipaddr-resolv.sh +97 -0
  17. data/bin/mac-drop +84 -0
  18. data/bin/mac-nodrop +45 -0
  19. data/bin/port-shutdown +78 -0
  20. data/bin/setup +8 -0
  21. data/lib/netutils.rb +118 -0
  22. data/lib/netutils/arp.rb +28 -0
  23. data/lib/netutils/cli.rb +702 -0
  24. data/lib/netutils/cli/alaxala.rb +121 -0
  25. data/lib/netutils/cli/alaxala/interface.rb +137 -0
  26. data/lib/netutils/cli/alaxala/lldp.rb +166 -0
  27. data/lib/netutils/cli/alaxala/macfib.rb +62 -0
  28. data/lib/netutils/cli/alaxala/showarp.rb +51 -0
  29. data/lib/netutils/cli/alaxala/showroute.rb +86 -0
  30. data/lib/netutils/cli/alaxala/showvrf.rb +46 -0
  31. data/lib/netutils/cli/aruba.rb +15 -0
  32. data/lib/netutils/cli/cisco.rb +45 -0
  33. data/lib/netutils/cli/cisco/cdp.rb +117 -0
  34. data/lib/netutils/cli/cisco/ifsummary.rb +32 -0
  35. data/lib/netutils/cli/cisco/interface.rb +67 -0
  36. data/lib/netutils/cli/cisco/macfib.rb +38 -0
  37. data/lib/netutils/cli/cisco/showarp.rb +27 -0
  38. data/lib/netutils/cli/cisco/showinterface.rb +27 -0
  39. data/lib/netutils/cli/cisco/showroute.rb +73 -0
  40. data/lib/netutils/cli/cisco/showvrf.rb +45 -0
  41. data/lib/netutils/cli/nec.rb +20 -0
  42. data/lib/netutils/cli/nec/lldp.rb +16 -0
  43. data/lib/netutils/cli/paloalto.rb +21 -0
  44. data/lib/netutils/fsm.rb +43 -0
  45. data/lib/netutils/macaddr.rb +51 -0
  46. data/lib/netutils/oncequeue.rb +78 -0
  47. data/lib/netutils/parser.rb +30 -0
  48. data/lib/netutils/rib.rb +80 -0
  49. data/lib/netutils/switch.rb +402 -0
  50. data/lib/netutils/tunnel.rb +8 -0
  51. data/lib/netutils/version.rb +3 -0
  52. data/lib/netutils/vrf.rb +42 -0
  53. data/log/.gitignore +1 -0
  54. data/netutils.gemspec +33 -0
  55. 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
@@ -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