netutils 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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,84 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.expand_path(File.dirname(__FILE__)).untaint, '/..')
3
+
4
+ require 'netutils'
5
+
6
+ ################################################################################
7
+ def usage
8
+ progname = File.basename($0)
9
+ STDERR.print "\
10
+ Usage: ruby #{progname} [-h] [-d] <IP address>
11
+ Options:
12
+ -d: dry run (do not shut down a port, just locate a host only)
13
+ -h: output this help message.
14
+ Example:
15
+ #{progname} 192.168.0.1
16
+ "
17
+ exit
18
+ end
19
+
20
+ case ARGV.size
21
+ when 1
22
+ usage if ARGV[0] !~ /^[0-9\.]+$/
23
+ when 2
24
+ usage if ARGV.shift != '-d'
25
+ dry = true
26
+ else
27
+ usage
28
+ end
29
+ usage if ARGV.size != 1
30
+
31
+ ia = ARGV[0]
32
+
33
+ swname = swia = xia = ma = 'unknown'
34
+ begin
35
+ log "locate directly connected router for #{ia}... "
36
+ sw, xia = router_locate(ia)
37
+ swname = sw.name
38
+ swia = sw.ia
39
+ if ia == xia
40
+ log "\t\"#{sw.name}\" (#{sw.ia})"
41
+ else
42
+ log "\t\"department router\" (#{xia})"
43
+ end
44
+
45
+ log_without_newline "resolving MAC address for #{xia}... "
46
+ ma, vrf, interface = sw.macaddr_resolve(xia)
47
+ log "found"
48
+ log "\t#{xia} #{ma} on VRF \"#{vrf.name}\" #{interface}"
49
+
50
+ vlan = interface_name_vlan_id(interface)
51
+ log_without_newline "setting to \"#{sw.name}\" (#{sw.ia})\n"
52
+
53
+ if dry
54
+ log 'skip (due to -d, dry run, option)'
55
+ exit 0
56
+ else
57
+ sw.configure
58
+
59
+ for vid in vlans_by_switch_name(swname, vlan) do
60
+ cmd = "mac-address-table static #{ma} vlan #{vid} drop"
61
+ log_without_newline "\t#{cmd}"
62
+ sw.cmd(cmd)
63
+ end
64
+
65
+ sw.unconfigure
66
+ log 'done'
67
+
68
+ s = File.join(File.dirname(__FILE__), 'mac-no-drop.rb')
69
+ s = File.expand_path(s)
70
+ log "please run below command on recovery:\n"
71
+ log "\t#{s} #{sw.name} #{sw.ia} #{ma} #{vlan}\n"
72
+ end
73
+ exitcode = 0
74
+ rescue => e
75
+ r = ' FAILED'
76
+ log "\n#{r}: #{e.to_s}"
77
+ log "ERROR: Cannot MAC DROP #{xia} (#{ma}) on #{swname} #{swia} for " +
78
+ "#{ia}"
79
+ exitcode = 1
80
+ end
81
+
82
+ mail "MAC DROP#{r}: #{swname} (#{swia}) for #{xia} (#{ma})", log_buffer
83
+
84
+ exit exitcode
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.expand_path(File.dirname(__FILE__)).untaint, '/..')
3
+
4
+ require 'netutils'
5
+
6
+ ################################################################################
7
+ def usage
8
+ progname = File.basename($0)
9
+ STDERR.print "\
10
+ Usage: #{progname} <Switch name> <IP address> <MAC address> <existing VLAN>
11
+ Example:
12
+ #{progname} hoge-cisco-01 192.168.0.1 de:ad:be:ef:de:ad
13
+ "
14
+ exit
15
+ end
16
+
17
+ def mac_nodrop(name, ia, mac, vlan)
18
+ sw = Switch.new(name, ia)
19
+ sw.login
20
+ sw.configure
21
+ for vid in vlans_by_switch_name(name, vlan) do
22
+ cmd = "no mac-address-table static #{mac} vlan #{vid}"
23
+ log_without_newline "\t#{cmd}\n"
24
+ sw.cmd(cmd)
25
+ end
26
+ sw.unconfigure
27
+ end
28
+
29
+ usage if ARGV.size != 4
30
+ name = ARGV[0]
31
+ ia = ARGV[1]
32
+ mac = ARGV[2]
33
+ vlan = ARGV[3]
34
+
35
+ begin
36
+ log_without_newline "setting to \"#{name}\"(#{ia})\n\n"
37
+ mac_nodrop(name, ia, mac, vlan)
38
+ log "\nRe-enable #{mac} on #{name} #{ia}"
39
+ rescue => e
40
+ r = ' FAILED'
41
+ log e.to_s
42
+ log "\ncannot allow traffic #{mac} on #{name} #{ia}"
43
+ end
44
+
45
+ mail "MAC NODROP#{r}: #{name} #{ia} #{mac}", log_buffer
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift File.join(File.expand_path(File.dirname(__FILE__)).untaint, '/..')
3
+
4
+ require 'netutils'
5
+
6
+ ################################################################################
7
+ def usage
8
+ STDERR.print "\
9
+ Usage:
10
+ #{$progname} [-d] (up|down) <Switch IP address> <port>
11
+ Example:
12
+ #{$progname} up 192.168.0.1 GigabitEthernet 1/2/3
13
+ #{$progname} down 192.168.0.2 GigabitEthernet 1/2/3
14
+ "
15
+ exit 1
16
+ end
17
+
18
+ #
19
+ if ARGV[0] === '-d'
20
+ ARGV.shift
21
+ dry = true
22
+ end
23
+ usage if ARGV.size < 3
24
+
25
+ #
26
+ cmd = ARGV.shift
27
+ ia = ARGV.shift
28
+ port = ARGV.shift
29
+ while ARGV.size > 0
30
+ port += ' ' + ARGV.shift
31
+ end
32
+
33
+ #
34
+ case cmd
35
+ when 'up'
36
+ msg = 'bringing up'
37
+ when 'down'
38
+ msg = 'shutting down'
39
+ else
40
+ usage
41
+ end
42
+
43
+ name = 'unknown'
44
+ begin
45
+ log_without_newline "Connecting to #{ia}... "
46
+ sw = Switch.new(nil, Switch::Type::ROUTER, ia)
47
+ sw.login
48
+ log 'done'
49
+ name = sw.name
50
+
51
+ port = sw.interface_name(port)
52
+ interface_sanity_check(name, port)
53
+ log_without_newline "#{msg.capitalize} #{port} on #{name} (#{ia})... "
54
+
55
+ if dry
56
+ log 'skip (due to -d, dry run, option)'
57
+ exit 0
58
+ elsif cmd === 'down'
59
+ sw.interface_shutdown(port)
60
+ s = File.expand_path(__FILE__)
61
+ log 'done'
62
+ log "please run below command on recovery:\n"
63
+ log "\t#{s} up #{ia} \'#{port}\'\n"
64
+ else
65
+ sw.interface_noshutdown(port)
66
+ log 'done'
67
+ end
68
+ exitcode = 0
69
+ rescue => e
70
+ r = ' FAILED'
71
+ log "\n#{r}: #{e.to_s}"
72
+ log "ERROR: Cannot #{msg} #{port} on #{name} #{ia}"
73
+ exitcode = 1
74
+ end
75
+
76
+ mail "Port #{cmd.upcase}#{r}: #{name} #{ia} #{port}", log_buffer
77
+
78
+ exit exitcode
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,118 @@
1
+ require "netutils/version"
2
+ require 'mail'
3
+ require 'netutils/switch'
4
+ require 'config/config'
5
+ require 'config/passwd'
6
+
7
+ module Netutils
8
+
9
+ #
10
+ $progname = File.basename($0)
11
+
12
+ #
13
+ ACL_MAX_SEQ = 4294967294
14
+ #
15
+ $log = ''
16
+
17
+ def mail(s, b)
18
+ m = Mail.new do
19
+ delivery_method :smtp, address: MAILSERVER
20
+ from MAILFROM
21
+ to MAILTO
22
+ subject s
23
+ body b
24
+ end
25
+ m.charset = 'ascii'
26
+ m.deliver!
27
+ end
28
+
29
+ def valid_ip_address?(s)
30
+ return false if s !~ /^(?:[0-9]+\.){3}[0-9]+$/
31
+ results = s.split('.').collect { |i| i.to_i.between?(0, 255) }
32
+ return results.count(true) === 4
33
+ end
34
+
35
+ def interface_sanity_check(host, port)
36
+ # XXX: need more checks to detect backbone links.
37
+ if port !~ /^[gf]/i
38
+ raise "Suppress shutdown #{port}, which may be backbone link."
39
+ end
40
+ end
41
+
42
+ def log_without_newline(m)
43
+ print m
44
+ $log += m
45
+ end
46
+
47
+ def log(m)
48
+ puts m
49
+ $log += m + "\n"
50
+ end
51
+
52
+ def log_buffer
53
+ return $log
54
+ end
55
+
56
+ def vlans_by_switch_name(swname, vlan)
57
+ vlan = vlan.to_i
58
+ vlans = []
59
+ vlans = VLANS[swname].dup if VLANS.has_key?(swname)
60
+ vlans.unshift(vlan) if vlan && ! vlans.include?(vlan)
61
+ return vlans
62
+ end
63
+
64
+ def interface_name_vlan_id(name)
65
+ return $1 if name =~ /^vlan([0-9]+)$/i
66
+ nil
67
+ end
68
+
69
+ def static_neighbor_resolve(name, ifname)
70
+ key = "#{name}_#{ifname}"
71
+ n = STATIC_NEIGHBOR[key]
72
+ return nil if n.nil?
73
+ Switch.get(n[:name], Switch::Type::ROUTER, nil, nil, nil, n[:ia])
74
+ end
75
+
76
+ def tunnel_nexthop_resolve(sw, rt)
77
+ return rt.nh if rt.nh
78
+ c = CDP.new(nil)
79
+ c.parse(sw.cli.cmd("show cdp neighbors #{rt.interface} detail"))
80
+ c.ias[0]
81
+ end
82
+
83
+ def router_locate(ia)
84
+ root = SWITCHES[0]
85
+ sw = Switch.new(root[0], Switch::Type::ROUTER, root[1])
86
+ sw.login
87
+
88
+ while true
89
+ rts = sw.route_gets(ia)
90
+ raise "No route found for #{ia}" if ! rts
91
+ bestrt = nil
92
+ rts.each do |rt|
93
+ case rt.proto
94
+ when 'connected'
95
+ return sw, ia
96
+ when 'static', 'rip', 'ospf', 'bgp'
97
+ # just in case for redistributed routes.
98
+ next if rt.nh === nil && ! rt.tunnel?
99
+ bestrt = rt.compare(bestrt)
100
+ end
101
+ end
102
+ raise "No valid route found for #{ia}" if ! bestrt
103
+
104
+ if OTHER_NEXTHOPS.include?(bestrt.nh)
105
+ return sw, bestrt.nh
106
+ end
107
+
108
+ if bestrt.tunnel?
109
+ nh = tunnel_nexthop_resolve(sw, bestrt)
110
+ else
111
+ nh = bestrt.nh
112
+ end
113
+ sw = Switch.new(nil, Switch::Type::ROUTER, nh)
114
+ sw.login
115
+ end
116
+ end
117
+
118
+ end
@@ -0,0 +1,28 @@
1
+ require 'netutils/macaddr'
2
+
3
+ class ARPTable
4
+ class ARP
5
+ attr_reader :ia, :ma, :interface, :static
6
+
7
+ def initialize(ia, ma, interface, static)
8
+ @ia = ia
9
+ @ma = MACAddr.new(ma)
10
+ @interface = interface
11
+ @static = static
12
+ end
13
+ end
14
+
15
+ attr_reader :arps
16
+
17
+ def initialize
18
+ @arps = {}
19
+ end
20
+
21
+ def add(ia, ma, interface, static)
22
+ @arps[ia] = ARP.new(ia, ma, interface, static)
23
+ end
24
+
25
+ def [](ia)
26
+ return @arps[ia]
27
+ end
28
+ end
@@ -0,0 +1,702 @@
1
+ require 'net/ssh'
2
+ require 'net/ssh/telnet'
3
+ require 'net/telnet'
4
+
5
+ path = File.expand_path(File.dirname(__FILE__)).untaint
6
+ Dir.glob("#{path}/cli/*") do |path|
7
+ file = File.basename(path, '.*')
8
+ require "netutils/cli/#{file}"
9
+ end
10
+
11
+ class CLI
12
+ module Maker
13
+ CISCO = 0
14
+ ALAXALA = 1
15
+ PALOALTO = 2
16
+ ARUBA = 3
17
+ NEC = 4
18
+ #
19
+ # XXX: Cisco WLC should come after Paloalto and NEC for a maker
20
+ # detection by a command to disable a CLI terminal pager.
21
+ # Since Paloalto always interprets a command like:
22
+ #
23
+ # ``config hogehoge''
24
+ #
25
+ # as:
26
+ #
27
+ # ``configure.''
28
+ #
29
+ # WLC pager command is then accepted by Paloalto and a
30
+ # maker detection fails if Cisco WLC preceds Paloalto.
31
+ #
32
+ WLC = 5 # XXX: Cisco WLC, should be product...
33
+ UNKNOWN = 6
34
+ MIN = CISCO
35
+ MAX = ARUBA
36
+ end
37
+
38
+ FIRST_PROMPT_RE = /^.*\n+\(?!?([^\r\n\(\)\s>#]+)\)? ?([>#]) ?\r?\n?.*$/m
39
+
40
+ TIMEOUT = 30
41
+ LOGIN_TIMEOUT = 3
42
+
43
+ attr_reader :name, :ia, :maker, :product, :users, :prompt,
44
+ :passwds, :enable_passwds
45
+
46
+ def initialize(name, ia, types = CLI_SESSION_TYPES, maker = Maker::UNKNOWN)
47
+ @ia = ia
48
+ @users = USERS.dup
49
+ @passwds = PASSWORDS.dup
50
+ @enable_passwds = ENABLES.dup
51
+ @enabled = false
52
+ @type = nil
53
+ @types = types
54
+ @session = nil
55
+ @maker = maker
56
+ @product = nil
57
+ @cr = ''
58
+ @name_supplied = name
59
+ name_update(nil)
60
+ @userprompt = [
61
+ 'login:', # Alaxala
62
+ '[Uu]sername:', # Cisco
63
+ 'User:' # Cisco WiSM/WLC
64
+ ]
65
+ # Cisco has trailing space, ``Password: '', but Alaxala not
66
+ @passwdprompt = 'Password:'
67
+ @telnetopt = Hash.new
68
+ @telnetopt['Timeout' ] = TIMEOUT
69
+ if defined?(LOGDIR)
70
+ path = File.dirname(__FILE__)
71
+ path += "/../"
72
+ path += "/#{LOGDIR}/#{ia}.log"
73
+ path = File.expand_path(path).untaint
74
+ @telnetopt['Output_log'] = path
75
+ end
76
+ #@telnetopt['Dump_log'] = '/dev/stdout'
77
+ end
78
+
79
+ def name_update(name)
80
+ if name =~ /^([^@]*)@(.*)$/
81
+ @user = $1
82
+ name = $2
83
+ end
84
+ raise "Invalid host name given: \"#{name}\"" if name =~ /[>#\n]/
85
+ if @name === nil && name != nil &&
86
+ @name_supplied != nil && name != @name_supplied
87
+ raise(ArgumentError, "host name mismatch: " +
88
+ "\"#{@name_supplied}\" is supplied " +
89
+ "but actually \"#{name}\"")
90
+ end
91
+ @name = name
92
+ case @maker
93
+ when Maker::CISCO
94
+ prefix = suffix = trailer = ''
95
+ when Maker::ALAXALA
96
+ prefix = '!?'
97
+ suffix = ''
98
+ trailer = "\0? "
99
+ when Maker::PALOALTO
100
+ raise('Invalid host name for Paloalto') if ! @user
101
+ prefix = "#{@user}@"
102
+ suffix = ''
103
+ trailer = ' '
104
+ when Maker::ARUBA
105
+ prefix = '\('
106
+ suffix = '\) '
107
+ trailer = ''
108
+ else
109
+ prefix = '\(?'
110
+ if @user
111
+ prefix = "#{prefix}#{@user}@"
112
+ else
113
+ prefix = "#{prefix}.*"
114
+ end
115
+ suffix = '\)? ?'
116
+ trailer = "\0? ?"
117
+ end
118
+ name = '[^\r\n\(\)]+' if ! name
119
+ @normalprompt = "#{prefix}#{name}#{suffix}>#{trailer}"
120
+ @enableprompt = "#{prefix}#{name}#{suffix}##{trailer}"
121
+ @configprompt = "#{prefix}#{name}#{suffix}\\(config[^ ]*\\)##{trailer}"
122
+ if @enabled
123
+ @prompt = @enableprompt
124
+ else
125
+ # XXX configuring node... dirty....
126
+ @prompt = @normalprompt
127
+ end
128
+ end
129
+
130
+ #
131
+ # handle a carriage return (\r) and (\E[K) that intend to
132
+ # remove all characters in a current line and remove trailing
133
+ # characters, respectively.
134
+ #
135
+ def handle_control_characters(input)
136
+ out = ''
137
+ l = ''
138
+ pos = 0
139
+ escape = nil
140
+ input.chars do |c|
141
+ if escape
142
+ escape += c
143
+ case escape
144
+ when /\e[0-9]/, /\e\[[0-9]+;[0-9]+H/, "\e[r"
145
+ escape = nil
146
+ when "\e[K"
147
+ l.slice!(pos, l.length - pos)
148
+ escape = nil
149
+ when "\x1b[6n"
150
+ # XXX: Alaxala edge switch hack...
151
+ escape = nil
152
+ end
153
+ next
154
+ end
155
+ case c
156
+ when "\0"
157
+ # XXX: i do not know but Alaxala sends this...
158
+ when "\b"
159
+ l.chop!
160
+ pos -= 1
161
+ when "\e"
162
+ escape = c
163
+ else
164
+ case c
165
+ when "\r"
166
+ pos = 0
167
+ when "\n"
168
+ #
169
+ # in case of "\r\n", do not override
170
+ # the character.
171
+ #
172
+ pos = l.length if pos === 0
173
+ l[pos] = c
174
+ out += l
175
+ l = ''
176
+ pos = 0
177
+ else
178
+ l[pos] = c
179
+ pos += 1
180
+ end
181
+ end
182
+ end
183
+ out += l if ! l.empty?
184
+ out.delete!("^\u{0001}-\u{007f}")
185
+ return out
186
+ end
187
+ private :handle_control_characters
188
+
189
+ def error?(r)
190
+ case r
191
+ when /%[^\n]+\n+\Z/m # XXX: this is not accurate
192
+ maker = Maker::CISCO
193
+ when /Error: Bad command\. \n\Z/m,
194
+ /[^\s]+: not found\n\Z/m,
195
+ /% The command or parameter at the ^ marker is invalid\./m,
196
+ /Error: Invalid parameter\./m
197
+ maker = Maker::ALAXALA
198
+ when /Invalid syntax\..*\Z/m, /Unknown command: .*\Z/m
199
+ maker = Maker::PALOALTO
200
+ when /^ *\^ *\n% [^\n]+ error/m,
201
+ /^ *\^ *\n% Invalid input detected at/m # same as Cisco
202
+ maker = Maker::ARUBA
203
+ else
204
+ return false
205
+ end
206
+ #maker_update(maker)
207
+ return true
208
+ end
209
+
210
+ def puts(s)
211
+ @session.puts(s + @cr)
212
+ end
213
+ private :puts
214
+
215
+ def cmd(s, nextprompt = nil, ignoreerror = false)
216
+ re = [ @prompt ]
217
+ re.push('#') if @maker == Maker::UNKNOWN # XXX dirty hack for now...
218
+ re.push(nextprompt) if nextprompt
219
+ re = '(?:' + re.join('|') + ')'
220
+ r = @session.cmd('String' => s + @cr, 'Match' => /#{re}\Z/)
221
+ r = handle_control_characters(r)
222
+ #
223
+ # XXX: allows a command not to be echo-ed like FTP server.
224
+ #
225
+ if r !~ /^#{re}?(?:#{s.sub('*', '\\*')})?\n+(.*)#{re}\Z/m
226
+ raise(ArgumentError, "CLI output error: \"#{r}\"")
227
+ end
228
+ r = $1
229
+ r.slice!(-1) if @maker === Maker::ALAXALA && r[-1] === '!'
230
+ if ignoreerror === false && error?(r)
231
+ raise(ArgumentError,
232
+ "Command failed on #{@name}: #{s}: #{r}")
233
+ end
234
+ @prompt = nextprompt if nextprompt
235
+ return r
236
+ end
237
+
238
+ def passwd(users, passwds, opasswds, nextprompt, cmd = nil)
239
+ userpromptre = /(?:#{@userprompt.join('|')})/
240
+ prompts = @userprompt.dup
241
+ prompts << @prompt if @prompt
242
+ prompts << @passwdprompt if @passwdprompt
243
+ prompts << nextprompt if nextprompt
244
+ re = /(?:#{prompts.join('|')})/
245
+ r = @session.waitfor('Match' => re) if users
246
+ while true
247
+ if passwds.empty?
248
+ users.shift if users
249
+ if users && r =~ userpromptre
250
+ passwds = opasswds.dup
251
+ else
252
+ raise(Errno::EPERM,
253
+ 'authentication failed')
254
+ end
255
+ end
256
+ if users && ! users.empty? && r =~ userpromptre
257
+ puts(users[0])
258
+ elsif cmd && r !~ /#{@passwdprompt}/
259
+ puts(cmd)
260
+ end
261
+ if r !~ /#{@passwdprompt}/
262
+ r = @session.waitfor(
263
+ 'Match' => /#{@passwdprompt}/)
264
+ #
265
+ # emulate an exception because net/telnet.rb
266
+ # does not raise an exception when a remote
267
+ # note disconnects after consecutive login
268
+ # failures.
269
+ #
270
+ raise(Errno::ECONNRESET) if r === nil
271
+ end
272
+ puts(passwds[0])
273
+ r = @session.waitfor('Match' => re)
274
+ case r
275
+ when userpromptre
276
+ raise('no user for user prompt') if ! users
277
+ when /#{@passwdprompt}/
278
+ when /#{nextprompt}/
279
+ @prompt = nextprompt
280
+ return r
281
+ else
282
+ if @prompt && r =~ /#{@prompt}/ && ! cmd
283
+ raise('invalid state')
284
+ end
285
+ end
286
+ passwds.shift
287
+ end
288
+ end
289
+ private :passwd
290
+
291
+ def login_ssh(ousers, opasswds)
292
+ users = ousers.dup
293
+ passwds = opasswds.dup
294
+ user = users.shift
295
+ begin
296
+ passwd = passwds.shift
297
+ opt = @telnetopt.dup
298
+ ssh = Net::SSH.start(@ia, user,
299
+ :password => passwd,
300
+ :non_interactive => true,
301
+ #:keys => ['/path/to/private_key'],
302
+ #:port => 22,
303
+ :timeout => opt['Timeout']
304
+ )
305
+ opt['Session'] = ssh
306
+
307
+ #
308
+ # XXX: Alaxala edge switch hack...
309
+ #
310
+ # an Alaxala edge switch asks us cursor position
311
+ # using CSI escap sequence, DSR (0x1b[6n), before
312
+ # outputing prompt. Net::SSH::Telnet then does not
313
+ # consider the case where escape sequence is sent
314
+ # right after login. This incurs long delay until
315
+ # Alaxala outputs the first prompt. Here, we send
316
+ # back a dummy cursor position, (1, 1), to Alaxala.
317
+ #
318
+ opt['Prompt'] = /(?:\e\[6n|[$%#>] ?\Z)/
319
+
320
+ msg = ''
321
+ @session = Net::SSH::Telnet.new(opt) { |l| msg += l }
322
+ @session.prompt = /[$%#>] \z/
323
+ if msg =~ /ALAXALA/
324
+ maker_update(Maker::ALAXALA)
325
+ if msg.rindex("\x1b[6n")
326
+ @cr = "\r"
327
+ @session.write("^1;1R")
328
+ msg += @session.waitfor(/[$%#>] \z/)
329
+ end
330
+ end
331
+ rescue Net::SSH::AuthenticationFailed, Net::SSH::Disconnect => e
332
+ if passwds.empty?
333
+ if users.empty?
334
+ raise(Errno::EPERM, "cannot login to " +
335
+ "#{@ia}")
336
+ end
337
+ user = users.shift
338
+ passwds = opasswds.dup
339
+ end
340
+ retry
341
+ rescue Net::SSH::ConnectionTimeout
342
+ raise("cannot login to #{@ia}")
343
+ end
344
+ #
345
+ # XXX: dirty hack
346
+ #
347
+ # some switch, say Alaxala, disconnects immediately
348
+ # after authentication succeeds if many user logged
349
+ # in. Net::SSH::Telnet.new() unfortunately cannot
350
+ # handle such case.
351
+ #
352
+ if @session.nil?
353
+ raise(Errno::ECONNRESET, 'too many users???')
354
+ end
355
+ return msg
356
+ end
357
+ private :login_ssh
358
+
359
+ def login_telnet(users, passwds)
360
+ opt = @telnetopt.dup
361
+ opt['Host'] = @ia
362
+ users = users.dup if users
363
+ passwds = passwds.dup
364
+ opasswds = passwds.dup
365
+ begin
366
+ @session = Net::Telnet::new(opt)
367
+ r = passwd(users, passwds, opasswds, @normalprompt)
368
+ rescue Errno::ECONNRESET
369
+ retry
370
+ end
371
+ return r
372
+ end
373
+ private :login_telnet
374
+
375
+ def login
376
+ users = @users.dup
377
+ passwds = @passwds.dup
378
+ @type = @types[0]
379
+ begin
380
+ r = send("login_#{@type}", users, passwds)
381
+ rescue Errno::ECONNREFUSED => e
382
+ raise e if @type === @types.last
383
+ @type = @types[@types.index(@type) + 1]
384
+ retry
385
+ end
386
+ r = handle_control_characters(r)
387
+ if r =~ FIRST_PROMPT_RE
388
+ name_update($1)
389
+ if $2 === '#'
390
+ @enabled = true
391
+ end
392
+ end
393
+ if r =~ /NEC Corporation.*OpenROUTE.*J\. Noel Chiappa/m
394
+ maker_update(Maker::NEC)
395
+ end
396
+ product_detect
397
+ end
398
+
399
+ PRODUCT_DETECTION_MAXRETRIES = 3
400
+
401
+ # XXX: exclude maker dependencies...
402
+ def product_detect
403
+ pager_disable
404
+ return if @maker === Maker::UNKNOWN
405
+ case @maker
406
+ when Maker::CISCO
407
+ c = 'show version'
408
+ # XXX: Nexus not supported yet
409
+ re = /IOS Software, ([^\s]+) Software .*/m
410
+ when Maker::WLC
411
+ c = 'show sysinfo'
412
+ re = /.*Product Name[^ ]+ (.*)$/m
413
+ when Maker::ALAXALA
414
+ c = 'show version'
415
+ re = /.*Model:\s+(AX[^\s\n]+).*$/m
416
+ when Maker::PALOALTO
417
+ c = 'show system info'
418
+ re = /model: ([^\s\n]+).*/m
419
+ when Maker::ARUBA
420
+ c = 'show version'
421
+ re = /.*ArubaOS \(MODEL: ([^\)]+)\),.*/m
422
+ when Maker::NEC
423
+ c = 'show version'
424
+ re = /^IX Series ([^ ]+) .*$/m
425
+ when Maker::UNKNOWN
426
+ return
427
+ end
428
+
429
+ leftchance = PRODUCT_DETECTION_MAXRETRIES
430
+ begin
431
+ v = cmd(c)
432
+ if v !~ re
433
+ raise('Unknown maker detected')
434
+ end
435
+ @product = $1
436
+ disable_logging_console
437
+ rescue => e
438
+ leftchance -= 1
439
+ raise e if leftchance === 0
440
+ retry
441
+ end
442
+ end
443
+ private :product_detect
444
+
445
+ def maker_to_s
446
+ # XXX: smarter way please...
447
+ case @maker
448
+ when Maker::CISCO
449
+ 'Cisco'
450
+ when Maker::WLC
451
+ 'WLC'
452
+ when Maker::ALAXALA
453
+ 'Alaxala'
454
+ when Maker::PALOALTO
455
+ 'Paloalto'
456
+ when Maker::ARUBA
457
+ 'Aruba'
458
+ when Maker::NEC
459
+ 'NEC'
460
+ else
461
+ 'Unknown'
462
+ end
463
+ end
464
+
465
+ def yes_or_no(yes_or_no, re)
466
+ re = /\n[^\n]+[Yy]\/[Nn]\)? ?$/m if re == nil
467
+ @session.waitfor('Match' => re)
468
+ puts(yes_or_no)
469
+ end
470
+ private :yes_or_no
471
+
472
+ def yes(re = nil)
473
+ yes_or_no('y', re)
474
+ end
475
+
476
+ def no(re = nil)
477
+ yes_or_no('n', re)
478
+ end
479
+
480
+ def logout
481
+ @session.close
482
+ end
483
+
484
+ def maker_update(maker)
485
+ return if @maker != Maker::UNKNOWN
486
+ @maker = maker
487
+ extend Module.const_get("#{maker_to_s}")
488
+ end
489
+ private :maker_update
490
+
491
+ def pager_disable_cisco
492
+ cmd('terminal length 0')
493
+ end
494
+ private :pager_disable_cisco
495
+
496
+ def pager_disable_wlc
497
+ cmd('config paging disable')
498
+ end
499
+
500
+ def pager_disable_alaxala
501
+ cmd('set terminal pager disable')
502
+ end
503
+ private :pager_disable_alaxala
504
+
505
+ def pager_disable_paloalto
506
+ cmd('set cli pager off')
507
+ end
508
+ private :pager_disable_paloalto
509
+
510
+ def pager_disable_aruba
511
+ enable
512
+ cmd('no paging')
513
+ end
514
+ private :pager_disable_aruba
515
+
516
+ def pager_disable_nec
517
+ configure
518
+ cmd('terminal length 0')
519
+ unconfigure
520
+ end
521
+ private :pager_disable_nec
522
+
523
+ def pager_disable
524
+ maker = omaker = @maker
525
+ maker = Maker::MIN if omaker === Maker::UNKNOWN
526
+ begin
527
+ @maker = maker
528
+ send('pager_disable_' + maker_to_s.downcase)
529
+ rescue Timeout::Error => e
530
+ raise e
531
+ rescue => e
532
+ if omaker === Maker::UNKNOWN
533
+ maker += 1
534
+ if maker != Maker::UNKNOWN &&
535
+ e != ArgumentError
536
+ retry
537
+ else
538
+ @maker = omaker
539
+ raise
540
+ end
541
+ end
542
+ ensure
543
+ @maker = omaker
544
+ end
545
+ maker_update(maker)
546
+ name_update(@name) if omaker === Maker::UNKNOWN
547
+ end
548
+ private :pager_disable
549
+
550
+ def enable
551
+ return if @enabled
552
+ passwd(nil, @enable_passwds.dup, @enable_passwds.dup,
553
+ @enableprompt.dup, 'enable')
554
+ @enabled = true
555
+ end
556
+
557
+ def configure
558
+ raise "already configuring" if @prompt == @configprompt
559
+ enable
560
+ return cmd('configure terminal', @configprompt)
561
+ end
562
+
563
+ def unconfigure
564
+ raise "currently not configuring" if @prompt != @configprompt
565
+ return cmd('end', @enableprompt)
566
+ end
567
+
568
+ def config_get
569
+ enable
570
+ re = Module.const_get(maker_to_s).const_get('CONFIG_RE')
571
+ if show_running_config !~ re
572
+ raise("Invalid configuration format for #{@name}")
573
+ end
574
+ return $1
575
+ end
576
+
577
+ def _new(name, *arg)
578
+ c = Module.const_get(maker_to_s)
579
+ if c.const_defined?(name)
580
+ c.const_get(name).new(*arg)
581
+ else
582
+ nil
583
+ end
584
+ end
585
+ private :_new
586
+
587
+ def route_gets(ia)
588
+ r = _new(:ShowRoute)
589
+ r.parse(cmd(r.cmd(ia), nil, true))
590
+ return r.rib.get(ia)
591
+ end
592
+
593
+ def vrf_gets
594
+ v = _new(:ShowVRF)
595
+ v.parse(cmd(v.cmd))
596
+ v.vrfs.add('default', '0:0') if v.vrfs.empty?
597
+ return v.vrfs
598
+ end
599
+
600
+ def arp_resolve(ia, vrf)
601
+ if vrf.name == 'default'
602
+ output = cmd("show ip arp #{ia}")
603
+ else
604
+ output = cmd("show ip arp vrf #{vrf.name} #{ia}")
605
+ end
606
+ a = _new(:ShowARP)
607
+ a.parse(output)
608
+ return a.arps[ia]
609
+ end
610
+
611
+ def mac_address_table_get(sw, ma, vlan)
612
+ fib = _new(:MACFIB, sw)
613
+ fib.parse(cmd(fib.cmd(ma, vlan)))
614
+ return fib.ports
615
+ end
616
+
617
+ def interface_gets(sw)
618
+ i = _new(:Interface, sw)
619
+ i.parse(cmd(i.cmd))
620
+ #
621
+ # XXX: hack for Cisco because Cisco cannot obtain up/down of
622
+ # an interface with interfaces capability command...
623
+ #
624
+ is = _new(:IfSummary, sw)
625
+ is.parse(cmd(is.cmd)) if is
626
+ end
627
+
628
+ def interface_name(port)
629
+ port
630
+ end
631
+
632
+ def interface_name_cli(port)
633
+ port
634
+ end
635
+
636
+ def interface_shutdown(port)
637
+ configure
638
+ cmd("interface #{port}")
639
+ cmd('shutdown')
640
+ unconfigure
641
+ end
642
+
643
+ def interface_noshutdown(port)
644
+ configure
645
+ cmd("interface #{port}")
646
+ cmd('no shutdown')
647
+ unconfigure
648
+ end
649
+
650
+ def acl_exists?(type, name)
651
+ configure
652
+ filters = cmd("show #{acl_definition(type, name)}")
653
+ unconfigure
654
+ ! filters.empty?
655
+ end
656
+
657
+ def acl_add(type, name, addr, seq = nil)
658
+ case type
659
+ when 'ip'
660
+ # XXX: we may need sanity check.
661
+ when 'mac', 'advance'
662
+ addr = MACAddr.new(addr)
663
+ else
664
+ raise("Unknown ACL type: #type")
665
+ end
666
+ cmd = acl_type_to_cmd(type)
667
+ configure
668
+ cmd(acl_definition(type, name))
669
+ cmd("#{seq} deny #{cmd} host #{addr} any")
670
+ unconfigure
671
+ end
672
+
673
+ def acl_delete(type, name, seq)
674
+ configure
675
+ cmd(acl_definition(type, name))
676
+ cmd("no #{seq}")
677
+ unconfigure
678
+ end
679
+
680
+ def neighbor_gets(sw, port = nil)
681
+ c = _new(:CDP, sw)
682
+ if c
683
+ begin
684
+ c.parse(cmd(c.cmd(port)))
685
+ return c.rsw if port && c.rsw
686
+ rescue ArgumentError => e
687
+ if e.to_s !~ /% CDP is not enabled/
688
+ raise e
689
+ end
690
+ end
691
+ end
692
+ l = _new(:LLDP, sw)
693
+ if l
694
+ l.parse(cmd(l.cmd(port)))
695
+ return l.rsw if port && l.rsw
696
+ end
697
+ if ! c && ! l
698
+ raise 'No method found for retrieving a neighbor!!'
699
+ end
700
+ nil
701
+ end
702
+ end