Class: Sloth::Snmp

Inherits:
Object
  • Object
show all
Defined in:
lib/sloth/snmp/impl.rb,
lib/sloth/snmp/version.rb

Defined Under Namespace

Classes: Error, UDPTransportExt

Constant Summary collapse

VERSION =
"0.1.1"

Instance Method Summary collapse

Constructor Details

#initialize(mibs: nil, bind: nil, rocommunity: "public", rwcommunity: "private") ⇒ Snmp

Returns a new instance of Snmp.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/sloth/snmp/impl.rb', line 21

def initialize( mibs: nil, bind: nil, rocommunity: "public", rwcommunity: "private" )
  case  bind
  when  Integer, NilClass
    @host  =  "0.0.0.0"
    @port  =  bind || 162
  when  String
    host, port  =  bind.split(':')    rescue  nil
    if host.nil? || host.empty?
      @host  =  "0.0.0.0"
    else
      @host  =  host
    end
    if port.nil? || port.empty?
      @port  =  162
    else
      @port  =  port.to_i
    end
  else
    raise  Sloth::Snmp::Error, "invalid class. : %s" % bind.class
  end
  @rocommunity  =  rocommunity
  @rwcommunity  =  rwcommunity

  @mibs  =  SNMP::MIB.new
  @mibs.load_module( "RFC1155-SMI" )
  @mibs.load_module( "RFC1158-MIB" )

  case  mibs
  when  NilClass
    mibs  =  []
  when  Array
    # noop
  when  String
    mibs  =  [mibs]
  else
    raise  Sloth::Snmp::Error, "invalid class. : %s" % mibs.class
  end

  mibpath  =  mibs.shift
  while  mibpath
    mibbase  =  File.basename( mibpath )
    mibdir  =  File.dirname( mibpath )
    if ( mibdir == "." && mibbase == mibpath )
      @mibs.load_module( mibbase.gsub( /\..*\Z/, "" ) )
    else
      @mibs.load_module( mibbase.gsub( /\..*\Z/, "" ), mibdir )
    end
    mibpath  =  mibs.shift
  end

  @topics  =  {}
  @mutex  =  ::Mutex.new
end

Instance Method Details

#build_varbind(topic, type, value) ⇒ Object



282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# File 'lib/sloth/snmp/impl.rb', line 282

def build_varbind( topic, type, value )
  oid  =  SNMP::ObjectId.new( topic )  rescue  @mibs.oid( topic )
  case  type
  when  SNMP::OctetString
    SNMP::VarBind.new( oid, SNMP::OctetString.new( value ))
  when  SNMP::Integer
    SNMP::VarBind.new( oid, SNMP::Integer.new( value ))
  when  SNMP::Counter32
    SNMP::VarBind.new( oid, SNMP::Counter32.new( value ))
  when  SNMP::IpAddress
    SNMP::VarBind.new( oid, SNMP::IpAddress.new( value ))
  when  SNMP::ObjectId
    SNMP::VarBind.new( oid, SNMP::ObjectId.new( value ))
  else
    SNMP::VarBind.new( oid, SNMP::OctetString.new( value.to_s ))
  end
end

#get(peer, topics, **options) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/sloth/snmp/impl.rb', line 135

def get( peer, topics, **options )
  host, port  =  peer.split(':')  rescue  nil
  host  =  "127.0.0.1"    if  host.nil? || host.empty?
  port  =  (port || 161).to_i
  community  =  options[:rocommunity]  ||  @rocommunity

  case  topics
  when  String
    oids  =  [ ( SNMP::ObjectId.new( topics )  rescue  @mibs.oid( topics ) ) ]
  when  Array
    oids  =  topics.map do |topic|
      SNMP::ObjectId.new( topic )  rescue  @mibs.oid( topic )
    end
  else
    raise  Sloth::Snmp::Error, "topics missing."
  end

  if  options[:bindto] && options[:device]
    transport  =  UDPTransportExt.new( Socket::AF_INET, bindto: options[:bindto], device: options[:device] )
  end

  manager  =  SNMP::Manager.new( host: host, port: port, community: community, transport: transport )

  items  =  {}
  begin
    response  =  Thread.handle_interrupt( ::Timeout::Error => :on_blocking ) do
      manager.get( oids )
    end

    response.each_varbind do |varbind|
      oid, item  =  * parse_varbind( varbind )
      items[oid]  =  item
    end

  rescue  SNMP::RequestTimeout => e
    raise  Sloth::Snmp::Error, e.message

  rescue  => e
    raise  Sloth::Snmp::Error, e.message

  end
  items
end

#parse_varbind(varbind, keys: nil) ⇒ Object



244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
# File 'lib/sloth/snmp/impl.rb', line 244

def parse_varbind( varbind, keys: nil )
  keys  =  [:name, :value]    if keys.nil? || keys.empty?
  oid   =  varbind.name.to_str
  name  =  @mibs.name( oid )
  case  varbind.value
  when  SNMP::OctetString
    type  =  SNMP::OctetString
    valu  =  varbind.value.to_s
    if  /[^[:print:]]/.match( valu )
      valu  =  "0x" + valu.unpack("H*").shift
    end
  when  SNMP::Integer
    type  =  SNMP::Integer
    valu  =  varbind.value.to_i
  when  SNMP::Counter32
    type  =  SNMP::Counter32
    valu  =  varbind.value.to_i
  when  SNMP::IpAddress
    type  =  SNMP::IpAddress
    valu  =  varbind.value.to_s
  when  SNMP::ObjectId
    type  =  SNMP::ObjectId
    valu  =  varbind.value
  when  SNMP::NoSuchInstance, SNMP::NoSuchObject
    type  =  varbind.value
    valu  =  nil
  else
    type  =  varbind.value
    valu  =  nil
  end

  tuple  =  {}
  tuple[:name]  =  name    if keys.include?( :name )
  tuple[:type]  =  type    if keys.include?( :type )
  tuple[:value]  =  valu    if keys.include?( :value )
  [oid, tuple]
end

#set(peer, tuple, **options) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/sloth/snmp/impl.rb', line 213

def set( peer, tuple, **options )
  host, port  =  peer.split(':')  rescue  nil
  host  =  "127.0.0.1"    if  host.nil? || host.empty?
  port  =  (port || 161).to_i
  community  =  options[:rwcommunity]  ||  @rwcommunity

  if  options[:bindto] && options[:device]
    transport  =  UDPTransportExt.new( Socket::AF_INET, bindto: options[:bindto], device: options[:device] )
  end

  varbind  =  build_varbind( tuple[:topic], tuple[:type], tuple[:value] )

  manager  =  SNMP::Manager.new( host: host, port: port, community: community, transport: transport )

  begin
    response  =  Thread.handle_interrupt( ::Timeout::Error => :on_blocking ) do
      manager.set( varbind )
    end
    response

  rescue  SNMP::RequestTimeout => e
    raise  Sloth::Snmp::Error, e.message

  rescue  => e
    raise  Sloth::Snmp::Error, e.message

  ensure
    manager.close
  end
end

#start_listenerObject



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/sloth/snmp/impl.rb', line 75

def start_listener
  @thread  ||=  Thread.start do
    listener  =  SNMP::TrapListener.new( host: @host, port: @port, community: @rocommunity ) do |manager|
      manager.on_trap_v2c do |mesg|
        begin
          next    if mesg.error_status  !=  :noError

          source_ip  =  mesg.source_ip
          trap_oid  =  mesg.trap_oid.join(".")
          trapname  =  @mibs.name( trap_oid )
          items  =  {}
          mesg.each_varbind do |varbind|
            oid, item  =  * parse_varbind( varbind )
            items[oid]  =  item
          end
          Thread.start do
            @mutex.synchronize do
              @topics[ trap_oid ]&.call( trapname, source_ip, items )
            end
          end

        rescue => e
          raise  Sloth::Snmp::Error, e.message

        end
      end
    end
    Thread.current[:listener]  =  listener
    listener.join
  end
end

#stop_listenerObject



107
108
109
110
111
112
113
# File 'lib/sloth/snmp/impl.rb', line 107

def stop_listener
  @mutex.synchronize do
    @topics.clear
    @thread[:listener].exit
    @thread  =  nil
  end
end

#trap(*topics, &block) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'lib/sloth/snmp/impl.rb', line 115

def trap( *topics, &block )
  @mutex.synchronize do
    topics.each do |topic|
      trap_oid  =  SNMP::ObjectId.new( topic )  rescue  @mibs.oid( topic )
      @topics[trap_oid.to_str]  =  block
    end
  end
  start_listener
end

#untrap(*topics) ⇒ Object



125
126
127
128
129
130
131
132
133
# File 'lib/sloth/snmp/impl.rb', line 125

def untrap( *topics )
  @mutex.synchronize do
    topics.each do |topic|
      trap_oid  =  SNMP::ObjectId.new( topic )  rescue  @mibs.oid( topic )
      @topics.delete( trap_oid.to_str )
    end
  end
  stop_listener    if @topics.empty?
end

#walk(peer, topic, **options) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/sloth/snmp/impl.rb', line 179

def walk( peer, topic, **options )
  host, port  =  peer.split(':')  rescue  nil
  host  =  "127.0.0.1"    if  host.nil? || host.empty?
  port  =  (port || 161).to_i
  community  =  options[:rocommunity]  ||  @rocommunity

  if  options[:bindto] && options[:device]
    transport  =  UDPTransportExt.new( Socket::AF_INET, bindto: options[:bindto], device: options[:device] )
  end

  manager  =  SNMP::Manager.new( host: host, port: port, community: community, transport: transport )

  topic  ||=  "internet"
  base_oid  =  SNMP::ObjectId.new( topic )  rescue  @mibs.oid( topic )

  items  =  {}
  begin
    Thread.handle_interrupt( ::Timeout::Error => :on_blocking ) do
      manager.walk( base_oid ) do |varbind|
        oid, item  =  * parse_varbind( varbind )
        items[oid]  =  item
      end
    end

  rescue  SNMP::RequestTimeout => e
    raise  Sloth::Snmp::Error, e.message

  rescue  => e
    raise  Sloth::Snmp::Error, e.message

  end
  items
end