-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathasync_session.pyx
1235 lines (968 loc) · 37.9 KB
/
async_session.pyx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
74
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
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
178
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
212
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
243
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
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
cimport cython
from cython.operator cimport dereference as deref
from libc.stdlib cimport free as libc_free
from posix.time cimport timeval
from libc.stdint cimport uint64_t
from libc.errno cimport EAGAIN
from cpython.dict cimport PyDict_Size
from cpython.bytes cimport PyBytes_FromStringAndSize
from cpython.version cimport PY_MAJOR_VERSION
import gevent
from gevent.socket import wait_read as gevent_wait_read
from gevent.socket import wait_write as gevent_wait_write
from gevent.socket import timeout as TimeoutError
from collections import OrderedDict
ctypedef unsigned int u_int
ctypedef unsigned char u_char
ctypedef unsigned long oid
ctypedef int (*select_func)(void*, timeval*)
cdef struct ax_async_ctx_t:
select_func func
void* ctx
cdef extern from "<net-snmp/net-snmp-config.h>":
pass
cdef extern from "<net-snmp/net-snmp-includes.h>":
pass
cdef extern from *:
## PDU interface ##
cdef enum:
SNMP_MSG_GET
SNMP_MSG_GETNEXT
SNMP_MSG_GETBULK
SNMP_MSG_SET
SNMP_MSG_TRAP
SNMP_MSG_TRAP2
MAX_OID_LEN
cdef enum:
ASN_OCTET_STR
ASN_INTEGER
ASN_NULL
ASN_OBJECT_ID
ASN_BIT_STR
ASN_IPADDRESS
ASN_COUNTER
ASN_GAUGE
ASN_TIMETICKS
ASN_COUNTER64
ASN_APP_FLOAT
ASN_APP_DOUBLE
SNMP_ENDOFMIBVIEW
SNMP_NOSUCHOBJECT
SNMP_NOSUCHINSTANCE
cdef struct counter64:
unsigned long high
unsigned long low
cdef union netsnmp_vardata:
long* integer
u_char* string
oid* objid
u_char* bitstring
counter64* counter64
float* floatVal
double* doubleVal
ctypedef struct netsnmp_variable_list:
netsnmp_variable_list* next_variable
oid *name
size_t name_length
u_char var_type "type"
netsnmp_vardata val
size_t val_len
ctypedef struct netsnmp_pdu:
int command
long reqid
# SNMPv2 Packet
long errstat
long errindex
# SNMPv1 Trap information
oid* enterprise
size_t enterprise_length
long trap_type
long specific_type
unsigned char* agent_addr
unsigned long time
# Generic Information
netsnmp_variable_list* variables
netsnmp_pdu* snmp_pdu_create(int)
void snmp_add_null_var(netsnmp_pdu*, const oid*, size_t)
int snmp_add_var(netsnmp_pdu*, const oid*, size_t, char, const char*)
void snmp_free_pdu(netsnmp_pdu*)
## Session interface ##
cdef enum:
SNMP_VERSION_1
SNMP_VERSION_2c
SNMP_VERSION_3
# V3 related defines.
cdef enum:
SNMP_SEC_LEVEL_NOAUTH
SNMP_SEC_LEVEL_AUTHNOPRIV
SNMP_SEC_LEVEL_AUTHPRIV
SNMP_SEC_MODEL_USM
# V3 related constats.
cdef oid* usmHMACMD5AuthProtocol
cdef size_t USM_AUTH_PROTO_MD5_LEN
cdef oid* usmHMACSHA1AuthProtocol
cdef size_t USM_AUTH_PROTO_SHA_LEN
cdef oid* usmDESPrivProtocol
cdef size_t USM_PRIV_PROTO_DES_LEN
cdef oid* usmAESPrivProtocol
cdef size_t USM_PRIV_PROTO_AES_LEN
cdef size_t USM_AUTH_KU_LEN
cdef size_t USM_PRIV_KU_LEN
cdef struct netsnmp_transport_s:
int sock
unsigned int flags
ctypedef struct netsnmp_session:
long version
int retries
long timeout
char* peername
u_char* community
size_t community_len
int s_snmp_errno
unsigned long flags
# v3 security options
int securityModel
int securityLevel
char* securityName
size_t securityNameLen
oid* securityAuthProto
size_t securityAuthProtoLen
oid* securityPrivProto
size_t securityPrivProtoLen
u_char* securityAuthKey
size_t securityAuthKeyLen
u_char* securityPrivKey
size_t securityPrivKeyLen
u_char* securityEngineID
size_t securityEngineIDLen
u_char* contextEngineID
size_t contextEngineIDLen
ax_async_ctx_t* myvoid
# v3 functions -- start
cdef int SNMPERR_SUCCESS
int generate_Ku(
# hashtype
const oid *,
# hashtype_len
u_int,
# password
u_char*,
# password len
size_t,
# KU
u_char*,
# KU len
size_t*)
u_int binary_to_hex(u_char*, size_t, char**)
int hex_to_binary2(u_char*, size_t, char**)
# v3 functions -- end
void snmp_sess_init(netsnmp_session*)
void* snmp_sess_open(netsnmp_session*)
void snmp_error(netsnmp_session*, int*, int*, char**)
# Works on the session pointer returned by snmp_sess_open
int snmp_sess_synch_response(void*, netsnmp_pdu*, netsnmp_pdu**)
netsnmp_transport_s* snmp_sess_transport(void*)
netsnmp_session* snmp_sess_session(void*)
void snmp_sess_error(void*, int*, int*, char**)
int snmp_sess_close(void*)
const char* snmp_errstring(int)
const char* snmp_api_errstring(int snmp_errnumber)
void init_snmp(char*)
# varbind API
int snprint_value(
char *buf,
size_t buf_len,
const oid * objid,
size_t objidlen,
const netsnmp_variable_list * variable)
int sprint_realloc_value(
u_char ** buf,
size_t * buf_len,
size_t * out_len,
int allow_realloc,
const oid * objid,
size_t objidlen,
const netsnmp_variable_list * variable)
void* snmp_out_toggle_options(char *options)
cdef enum:
STAT_SUCCESS
SNMP_ERR_NOERROR
SNMPERR_TIMEOUT
SNMPERR_BAD_SENDTO
cdef extern from "<dlfcn.h>" nogil:
void *dlsym(void*, const char*)
void* RTLD_DEFAULT
cdef extern from "<arpa/inet.h>" nogil:
enum:
AF_INET
INET_ADDRSTRLEN
const char *inet_ntop(int af, const void *src, char *dst, unsigned int size)
ctypedef int (*snmp_parse_func)(
void*,
netsnmp_session*,
netsnmp_pdu*,
unsigned char*,
size_t
)
# SNMP version 3 works only if this method is called once.
# Otherwise you get 'no such security service available' errors.
# It is also needed to format values according to MIB,
# because it loads all the MIBS.
def init_snmplib():
init_snmp('async_session')
def toggle_netsnmp_format_options(options):
for opt in options:
if snmp_out_toggle_options(opt) != NULL:
raise Exception("Option (%s) is not a valid format option" % opt)
@cython.internal
cdef class EndOfMib(object):
def __str__(self):
return "<END_OF_MIB>"
def __repr__(self):
return str(self)
@cython.internal
cdef class NoSuchObject(object):
def __str__(self):
return "<No Such Object>"
def __repr__(self):
return str(self)
@cython.internal
cdef class NoSuchInstance(object):
def __str__(self):
return "<No Such Instance>"
def __repr__(self):
return str(self)
END_OF_MIB = EndOfMib()
NO_SUCH_OBJECT = NoSuchObject()
NO_SUCH_INSTANCE = NoSuchInstance()
class SNMPError(Exception):
pass
class SNMPTimeoutError(SNMPError):
pass
class SNMPResponseError(SNMPError):
def __init__(self, code, index, message):
self.code = code
self.index = index
super(SNMPResponseError, self).__init__(
"Error at index(%s) with code(%s): %s" % (index, code, message))
@cython.internal
cdef class WriteWouldBlock(Exception):
pass
def oid_str_to_tuple(oid_str):
"""Converts a string like '1.2.3' to a tuple of integers like (1, 2, 3)"""
return tuple([int(idx) for idx in oid_str.split('.')])
def oid_tuple_to_str(oid_tuple):
"""Converts a tuple of integers like (1, 2, 3) to a sting like '1.2.3'"""
return '.'.join(map(str, oid_tuple))
cpdef is_in_subtree(root, oid):
if len(oid) < len(root):
return False
for index in range(len(root)):
if root[index] != oid[index]:
return False
return True
# These are the type specifications allowed by 'snmp_add_var'
# 'snmp_add_var' reads all the values as a *string* and parses them.
# shortcout, long name, (C type, ASN type, alias)
# 'i' 'INTEGER' (long, ASN_INTEGER)
# 'u' 'Unsigned32' (unsigned long, ASN_UNSIGNED, TYPE_GAUGE)
# '3' 'UInteger32' (unsigned long, ASN_UINTEGER)
# 'c' 'Counter32' (unsigned long, ASN_COUNTER)
# 'C' 'Counter64' (struct, ASN_COUNTER64)
# 't' 'TimeTicks' (unsigned long, ASN_TIMETICKS)
# 'a' 'IpAddress' (struct, ASN_IPADDRESS) => Ip as string is parsed
# 'o' 'Object' (oid, ASN_OBJECT_ID)
# 's' 'Octet str' (char*, ASN_OCTET_STR)
# 'd' 'decimal' (..., ASN_OCTET_STR) => decimal number is parsed
# 'x' 'hex' (..., ASN_OCTET_STR) => hex string is parsed
# 'n' 'null' (..., ASN_NULL)
# 'b' 'Bits' (..., ASN_OCTET_STR)
# 'U' (struct, ASN_OPAQUE_U64
# 'I' (struct, ASN_OPAQUE_I64
# 'F' (float, ASN_OPAQUE_FLOAT)
# 'D' (double, ASN_OPAQUE_DOUBLE)
VALID_VALUE_TYPES = set('iu3cCtaosdxnbUIFD')
VALUE_TYPE_TO_INT = {key: ord(key) for key in VALID_VALUE_TYPES}
VAR_TYPE_TO_STRING = {
ASN_OCTET_STR: "OCTET_STR",
ASN_INTEGER: "INTEGER",
ASN_NULL: "NULL",
ASN_OBJECT_ID: "OBJECT_ID",
ASN_BIT_STR: "BIT_STR",
ASN_IPADDRESS: "IPADDRESS",
ASN_COUNTER: "COUNTER",
ASN_GAUGE: "GAUGE",
ASN_TIMETICKS: "TIMETICKS",
ASN_COUNTER64: "COUNTER64",
ASN_APP_FLOAT: "APP_FLOAT",
ASN_APP_DOUBLE: "APP_DOUBLE"
}
cdef object error_from_session(msg, netsnmp_session* session):
cdef int p_errno
cdef int p_snmp_errno
cdef char* error_str
snmp_error(
session,
cython.address(p_errno),
cython.address(p_snmp_errno),
cython.address(error_str))
try:
if p_snmp_errno == SNMPERR_TIMEOUT:
return SNMPTimeoutError("%s: %s" % (msg, error_str))
else:
return SNMPError("%s: %s" % (msg, error_str))
finally:
libc_free(error_str)
cdef object error_from_session_ptr(msg, void* sp):
cdef int p_errno
cdef int p_snmp_errno
cdef char* error_str
snmp_sess_error(
sp,
cython.address(p_errno),
cython.address(p_snmp_errno),
NULL)
if p_snmp_errno == SNMPERR_BAD_SENDTO and p_errno == EAGAIN:
return WriteWouldBlock()
# Only construct the error string if needed.
snmp_sess_error(sp, NULL, NULL, cython.address(error_str))
try:
if p_snmp_errno == SNMPERR_TIMEOUT:
return SNMPTimeoutError("%s: %s" % (msg, error_str))
else:
return SNMPError("%s: %s" % (msg, error_str))
finally:
libc_free(error_str)
cdef int my_select(void* ctx, timeval* timeout):
# Error handling:
# If there is an exception in gevent_wait_read I CAN NOT just 'return -1'.
# It looks like netsnmp has a memory leak if 'select()' fails.
# Sometimes netsnmp frees the request pdu on error and sometimes not. And
# the user of the API has no chance to know if its already freed or not.
# So I can choose between memory leak or double free.
# For that reason I tell netsnmp that a timeout has happend.
# To still get the error message, the 'async_session' object has a variable
# called 'error_in_my_select'. As soon as this is not None an exception
# happend in 'gevent_wait_read' => The collection will fail.
cdef AsyncSession async_session = <AsyncSession>(ctx)
cdef int sock = snmp_sess_transport(async_session.sp).sock
py_timeout = timeout.tv_sec + (<double>(timeout.tv_usec) / 10**6)
# As soon as error_in_my_select don't wait for the socket.
# Return 0, which means timeout has happend
# Need to sleep, otherwise netsnmp calls select in a busy loop
if async_session.error_in_my_select is not None:
try:
gevent.sleep(py_timeout)
except Exception:
pass
return 0
try:
gevent_wait_read(sock, py_timeout)
except TimeoutError:
return 0
except Exception as error:
# As said above, tell netsnmp a timeout happend, but keep the error.
async_session.error_in_my_select = error
return 0
else:
return 1
@cython.final
cdef class AsyncSession(object):
# Need to keep a reference to the used strings like community and peername.
cdef object args
# Pointer to the snmp session object created via snmp_sess_open.
cdef void* sp
# If my_select raises an excpetion, keep the latest here.
cdef object error_in_my_select
# Counts how many SNMP interactions/packets were done on this session.
cdef uint64_t query_count
cdef ax_async_ctx_t ax_async_ctx
def __cinit__(self, args):
self.sp = NULL
self.query_count = 0
def __dealloc__(self):
if self.sp != NULL:
snmp_sess_close(self.sp)
def __init__(self, args):
self.args = args
self.error_in_my_select = None
self.ax_async_ctx.func = my_select
self.ax_async_ctx.ctx = <void*>(self)
# Read only access to snmp_interactions
property snmp_query_count:
def __get__(self):
return self.query_count
# Returns the securityEngineID as hex.
# Returns None if the id is not present.
property security_engine_id:
def __get__(self):
if self.sp == NULL:
return None
if snmp_sess_session(self.sp).securityEngineIDLen == 0:
return None
return binary_to_hex_pystring(
snmp_sess_session(self.sp).securityEngineID,
snmp_sess_session(self.sp).securityEngineIDLen)
# Returns the contextEngineID as hex.
# Returns None if the id is not present.
property context_engine_id:
def __get__(self):
if self.sp == NULL:
return None
if snmp_sess_session(self.sp).contextEngineIDLen == 0:
return None
return binary_to_hex_pystring(
snmp_sess_session(self.sp).contextEngineID,
snmp_sess_session(self.sp).contextEngineIDLen)
def open_session(self):
cdef netsnmp_session sess_cfg
snmp_sess_init(cython.address(sess_cfg))
sess_cfg.peername = <bytes?>(self.args['peername'])
sess_cfg.retries = self.args['retries']
sess_cfg.timeout = self.args['timeout'] * 1000000
# Uses the 'async select' from session->myvoid.
sess_cfg.flags |= 0x200000
if self.args['version'] == '1':
sess_cfg.version = SNMP_VERSION_1
self._set_community(cython.address(sess_cfg))
elif self.args['version'] == '2c':
sess_cfg.version = SNMP_VERSION_2c
self._set_community(cython.address(sess_cfg))
elif self.args['version'] == '3':
sess_cfg.version = SNMP_VERSION_3
self._set_version_three_auth(cython.address(sess_cfg))
else:
raise Exception("Unkown snmp version: %s" % self.args['version'])
self.sp = snmp_sess_open(cython.address(sess_cfg))
if self.sp == NULL:
raise error_from_session("Can not open", cython.address(sess_cfg))
snmp_sess_session(self.sp).myvoid = cython.address(self.ax_async_ctx)
# Ignores the fd_set within the snmp-api.
snmp_sess_transport(self.sp).flags |= 0x100000
cdef _set_community(self, netsnmp_session* sess_cfg):
sess_cfg.community = <bytes?>(self.args['community'])
sess_cfg.community_len = len(self.args['community'])
cdef _set_version_three_auth(self, netsnmp_session* sess_cfg):
cdef int res = 0
# For V3 the USM model is used.
sess_cfg.securityModel = SNMP_SEC_MODEL_USM
# Configure one of the three types of security supported.
security_level = self.args['security_level']
if security_level == 'noAuthNoPriv':
sess_cfg.securityLevel = SNMP_SEC_LEVEL_NOAUTH
elif security_level == 'authNoPriv':
sess_cfg.securityLevel = SNMP_SEC_LEVEL_AUTHNOPRIV
elif security_level == 'authPriv':
sess_cfg.securityLevel = SNMP_SEC_LEVEL_AUTHPRIV
else:
raise Exception("Unknown security_level: %s" % security_level)
# Username is mandatory.
sess_cfg.securityName = <bytes?>(self.args['security_name'])
sess_cfg.securityNameLen = len(self.args['security_name'])
# If auth is used, the protocol must be given.
if security_level == 'authNoPriv' or security_level == 'authPriv':
auth_proto = self.args['auth_proto']
if auth_proto == 'MD5':
sess_cfg.securityAuthProto = usmHMACMD5AuthProtocol
sess_cfg.securityAuthProtoLen = USM_AUTH_PROTO_MD5_LEN
elif auth_proto == 'SHA':
sess_cfg.securityAuthProto = usmHMACSHA1AuthProtocol
sess_cfg.securityAuthProtoLen = USM_AUTH_PROTO_SHA_LEN
else:
raise Exception("Unknown auth protocol: %s" % auth_proto)
sess_cfg.securityAuthKeyLen = USM_AUTH_KU_LEN
res = generate_Ku(
sess_cfg.securityAuthProto,
sess_cfg.securityAuthProtoLen,
<bytes?>(self.args['auth_key']),
len(self.args['auth_key']),
sess_cfg.securityAuthKey,
cython.address(sess_cfg.securityAuthKeyLen))
if res != SNMPERR_SUCCESS:
raise Exception("Can't generate KU for auth_key: %i" % res)
# if priv is used, the protocol must be given.
if security_level == 'authPriv':
priv_proto = self.args['priv_proto']
if priv_proto == 'DES':
sess_cfg.securityPrivProto = usmDESPrivProtocol
sess_cfg.securityPrivProtoLen = USM_PRIV_PROTO_DES_LEN
elif priv_proto == 'AES':
sess_cfg.securityPrivProto = usmAESPrivProtocol
sess_cfg.securityPrivProtoLen = USM_PRIV_PROTO_AES_LEN
else:
raise Exception("Unknown priv protocol: %s" % priv_proto)
sess_cfg.securityPrivKeyLen = USM_PRIV_KU_LEN
res = generate_Ku(
sess_cfg.securityAuthProto,
sess_cfg.securityAuthProtoLen,
<bytes?>(self.args['priv_key']),
len(self.args['priv_key']),
sess_cfg.securityPrivKey,
cython.address(sess_cfg.securityPrivKeyLen))
if res != SNMPERR_SUCCESS:
raise Exception("Can't generate KU for priv_key: %i" % res)
# security_engine_id is assumed as 'hex' format, but binary is needed.
if 'security_engine_id' in self.args:
bin_val = self.args['security_engine_id'].decode('hex')
# Need to store it somewhere, to keep reference to memory.
self.args['security_engine_id_binary'] = bin_val
sess_cfg.securityEngineID = <bytes?>(bin_val)
sess_cfg.securityEngineIDLen = len(bin_val)
# context_engine_id is assumed as 'hex' format, but binary is needed.
if 'context_engine_id' in self.args:
bin_val = self.args['context_engine_id'].decode('hex')
self.args['context_engine_id_binary'] = bin_val
sess_cfg.contextEngineID = <bytes?>(bin_val)
sess_cfg.contextEngineIDLen = len(bin_val)
def clone_session(self, **override_args):
return CloneSession(self, override_args)
@staticmethod
def decode_trap(bytes data, py_flags={}):
"""Decodes a SNMP-Trap received from a UDP socket."""
# All this dlsym can go away with net-snmp 5.8.
# net-snmp 5.8 exposes snmp_parse as a public symbol.
# Why this dslym ?? To avoid import errors if linked againts wrong
# net-snmp. This function gets only used in very rare situations.
# Thus most people can life with snmp_parse not exposed.
# -> Dont fail at import time with:
# ImportError: async_session.so: undefined symbol: snmp_parse
# Instead fail at runtime only when this function gets called.
cdef snmp_parse_func snmp_parse
snmp_parse = <snmp_parse_func> dlsym(RTLD_DEFAULT, "snmp_parse")
if snmp_parse == NULL:
raise Exception("snmp_parse not exposed by linked netsnmp library")
cdef netsnmp_session session
cdef netsnmp_pdu* pdu = snmp_pdu_create(0)
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
snmp_sess_init(cython.address(session))
try:
res = snmp_parse(NULL, cython.address(session), pdu, data, len(data))
if res != 0:
raise error_from_session("snmp_parse error", cython.address(session))
py_result = {}
py_result['varbinds'] = AsyncSession._parse_varbinds(pdu, flags)
py_result['request_id'] = pdu.reqid
py_result['msg_type_raw'] = pdu.command
if pdu.command == SNMP_MSG_TRAP2:
py_result['msg_type'] = 'v2_trap'
elif pdu.command == SNMP_MSG_TRAP:
py_result['msg_type'] = 'v1_trap'
py_result['trap_type'] = pdu.trap_type
py_result['specific_type'] = pdu.specific_type
py_result['agent_addr'] = convert_agent_addres(pdu.agent_addr)
py_result['uptime'] = pdu.time
py_result['enterprise'] = oid_ptr_as_tuple(
pdu.enterprise,
pdu.enterprise_length
)
else:
py_result['msg_type'] = 'unknown'
return py_result
finally:
snmp_free_pdu(pdu)
## These are 'higher level' functions.
def walk(self, root, py_flags={}):
"""Walks a tree by succesive calling get_next."""
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
final_result = {}
oid = root
while True:
oid = self._handle_walk_result(
self._send_getnext(oid),
root,
oid,
final_result,
flags)
if oid is None:
return final_result
def walk_with_get_bulk(self, root, maxrepetitions=10, py_flags={}):
"""Walks a *single* subtree by succesive calling get_bulk.
It does not walk multiple columns at the same time, its just are more
effienct implementation of walk() =>
The API (input params and output) is nearly identical to walk()
"""
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
final_result = {}
oid = root
while True:
oid = self._handle_walk_result(
self._send_getbulk([oid], 0, maxrepetitions),
root,
oid,
final_result,
flags)
if oid is None:
return final_result
# This is the private API for the 'high level' calls.
cdef _handle_walk_result(
self,
netsnmp_pdu* response,
root,
prev_oid,
final_result,
uint64_t flags):
"""Handles the results for walks.
Adds (oid, value) pairs from result to final_result
It returns the 'next' oid to query on.
It returns None if processing should stop
"""
next_oid = None
cdef netsnmp_variable_list* entry = NULL
try:
self._raise_on_response_error(response)
entry = response.variables
while (entry != NULL):
next_oid = AsyncSession.parse_var_key(entry)
if next_oid == prev_oid:
return None
if entry.var_type == SNMP_ENDOFMIBVIEW:
return None
if not is_in_subtree(root, next_oid):
return None
final_result[next_oid] = AsyncSession._parse_varbind(entry, flags)
entry = entry.next_variable
return next_oid
finally:
snmp_free_pdu(response)
## These are the 'low level' snmp functions.
def get(self, oids, py_flags={}):
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
try:
response = self._send_req(self._gen_get_pdu(oids))
return self._handle_response(response, flags)
except WriteWouldBlock:
self._wait_for_write()
response = self._send_req(self._gen_get_pdu(oids))
return self._handle_response(response, flags)
def get_next(self, py_oid, py_flags={}):
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
response = self._send_getnext(py_oid)
return self._handle_response(response, flags)
def get_bulk(
self,
oids,
nonrepeaters=0,
maxrepetitions=10,
py_flags={}):
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
response = self._send_getbulk(oids, nonrepeaters, maxrepetitions)
return self._handle_response(response, flags)
def set_oids(self, oids, py_flags={}):
cdef uint64_t flags = AsyncSession.gen_flags(py_flags)
try:
response = self._send_req(self._gen_set_pdu(oids))
return self._handle_response(response, flags)
except WriteWouldBlock:
response = self._send_req(self._gen_set_pdu(oids))
return self._handle_response(response, flags)
## Handling the flags
@staticmethod
cdef uint64_t gen_flags(dict py_flags) except -1:
cdef uint64_t flags = 0
if not PyDict_Size(py_flags):
return flags
if py_flags.get('get_var_type'):
flags = AsyncSession.set_get_vartype(flags)
if py_flags.get('get_end_of_mib'):
flags = AsyncSession.set_get_endofmib(flags)
if py_flags.get('get_no_such_object'):
flags = AsyncSession.set_get_nosuchobject(flags)
if py_flags.get('get_no_such_instance'):
flags = AsyncSession.set_get_nosuchinstance(flags)
if py_flags.get('as_ordered_dict'):
flags = AsyncSession.set_as_ordered_dict(flags)
if py_flags.get('get_netsnmp_string'):
flags = AsyncSession.set_get_netsnmp_string(flags)
if py_flags.get('as_list'):
flags = AsyncSession.set_as_list(flags)
return flags
# If the return value should have the low level ASN type included
@staticmethod
cdef inline uint64_t set_get_vartype(uint64_t flags):
return flags | (1 << 0)
@staticmethod
cdef inline uint64_t get_get_vartype(uint64_t flags):
return flags & (1 << 0)
# If we return the EndOfMib object or None
@staticmethod
cdef inline uint64_t set_get_endofmib(uint64_t flags):
return flags | (1 << 1)
@staticmethod
cdef inline uint64_t get_get_endofmib(uint64_t flags):
return flags & (1 << 1)
# If we return the NoSuchObject object or None
@staticmethod
cdef inline uint64_t set_get_nosuchobject(uint64_t flags):
return flags | (1 << 2)
@staticmethod
cdef inline uint64_t get_get_nosuchobject(uint64_t flags):
return flags & (1 << 2)
# If we return the NoSuchInstance object or None
@staticmethod
cdef inline uint64_t set_get_nosuchinstance(uint64_t flags):
return flags | (1 << 3)
@staticmethod
cdef inline uint64_t get_get_nosuchinstance(uint64_t flags):
return flags & (1 << 3)
# If the results should be ordered
@staticmethod
cdef inline uint64_t set_as_ordered_dict(uint64_t flags):
return flags | (1 << 4)
@staticmethod
cdef inline uint64_t get_as_ordered_dict(uint64_t flags):
return flags & (1 << 4)
# If the values should be formated by netsnmp
@staticmethod
cdef inline uint64_t set_get_netsnmp_string(uint64_t flags):
return flags | (1 << 5)
@staticmethod
cdef inline uint64_t get_get_netsnmp_string(uint64_t flags):
return flags & (1 << 5)
# If result should be list of (oid, value) instead of dict
@staticmethod
cdef inline uint64_t set_as_list(uint64_t flags):
return flags | (1 << 6)
@staticmethod
cdef inline uint64_t get_as_list(uint64_t flags):
return flags & (1 << 6)
## This is private API for the 'low level' calls.
cdef netsnmp_pdu* _gen_get_pdu(self, oids) except NULL:
cdef netsnmp_pdu* req = snmp_pdu_create(SNMP_MSG_GET)
if req == NULL:
raise MemoryError()
for py_oid in oids:
self._add_oid(req, py_oid)
return req
cdef netsnmp_pdu* _send_getnext(self, py_oid) except NULL:
try:
return self._send_req(self._gen_getnext_pdu(py_oid))
except WriteWouldBlock:
self._wait_for_write()
return self._send_req(self._gen_getnext_pdu(py_oid))
cdef netsnmp_pdu* _gen_getnext_pdu(self, py_oid) except NULL:
cdef netsnmp_pdu* req = snmp_pdu_create(SNMP_MSG_GETNEXT)
if req == NULL:
raise MemoryError()
self._add_oid(req, py_oid)
return req
cdef netsnmp_pdu* _send_getbulk(self, oids, nonrepeaters, maxrepetitions) except NULL:
try:
req = self._gen_getbulk_pdu(oids, nonrepeaters, maxrepetitions)
return self._send_req(req)
except WriteWouldBlock:
self._wait_for_write()
req = self._gen_getbulk_pdu(oids, nonrepeaters, maxrepetitions)
return self._send_req(req)
cdef netsnmp_pdu* _gen_getbulk_pdu(self, oids, nonrepeaters, maxrepetitions) except NULL:
cdef netsnmp_pdu* req = snmp_pdu_create(SNMP_MSG_GETBULK)
if req == NULL:
raise MemoryError()
req.errstat = nonrepeaters
req.errindex = maxrepetitions
for py_oid in oids:
self._add_oid(req, py_oid)
return req
cdef netsnmp_pdu* _gen_set_pdu(self, oids) except NULL:
cdef netsnmp_pdu* req = snmp_pdu_create(SNMP_MSG_SET)
if req == NULL:
raise MemoryError()
try:
for py_oid, (value, value_type) in oids.iteritems():
self._add_oid_set(req, py_oid, value, value_type)
except Exception:
snmp_free_pdu(req)
raise
else:
return req
cdef _wait_for_write(self):
gevent_wait_write(
snmp_sess_transport(self.sp).sock,
self.args['timeout']
)
cdef netsnmp_pdu* _send_req(self, netsnmp_pdu* req) except NULL:
if self.sp == NULL:
snmp_free_pdu(req)
raise SNMPError("Session is not open")
cdef netsnmp_pdu* response
self.error_in_my_select = None
self.query_count += 1
cdef int rc = snmp_sess_synch_response(
self.sp,
req,
cython.address(response))